https://www.yuque.com/books/share/1e287384-5c4d-439a-a4fd-dc8d65cce7b7?# 《Express 教程》
基本用法
Express 相关链接
- Express 官网
- Express GitHub 仓库
- Express 中文文档(非官方)
- Awesome Express
内置中间件
Express 具有以下内置中间件函数:
- express.json() 解析 Content-Type 为 application/json 格式的请求体
- express.urlencoded() 解析 Content-Type 为 application/x-www-form-urlencoded 格式的请求体
- express.raw() 解析 Content-Type 为 application/octet-stream 格式的请求体
- express.text() 解析 Content-Type 为 text/plain 格式的请求体
- express.static() 托管静态资源文件
第三方中间件
早期的 Express 内置了很多中间件。后来 Express 在 4.x 之后移除了这些内置中间件,官方把这些功能性中间件以包的形式单独提供出来。这样做的目的是为了保持 Express 本身极简灵活的特性,开发人员可以根据自己的需要去灵活的使用。
有关Express常用的第三方中间件功能的部分列表,请参阅:http://expressjs.com/en/resources/middleware.html。
快速路由器
使用 express.Router 该类创建模块化的,可安装的路由处理程序。一个 Router 实例是一个完整的中间件和路由系统;因此,它通常被称为“迷你应用程序”。
以下示例将路由器创建为模块,在其中加载中间件功能,定义一些路由,并将路由器模块安装在主应用程序的路径上。
birds.js 在 app 目录中创建一个名为以下内容的路由器文件:
var express = require('express')
var router = express.Router()
// middleware that is specific to this router
router.use(function timeLog (req, res, next) {
console.log('Time: ', Date.now())
next()
})
// define the home page route
router.get('/', function (req, res) {
res.send('Birds home page')
})
// define the about route
router.get('/about', function (req, res) {
res.send('About birds')
})
module.exports = router
然后,在应用程序中加载路由器模块:
var birds = require('./birds')
// ...
app.use('/birds', birds)
该应用程序现在将能够处理对 /birds 和的请求 /birds/about,以及调用 timeLog 特定于该路由的中间件功能。
Express 开发接口服务
案例介绍
- GitHub 仓库:https://github.com/gothinkster/realworld
- 客户端在线示例:https://demo.realworld.io/
- 接口文档:https://github.com/gothinkster/realworld/tree/master/api
RESTful 接口设计规范
参考链接
- http://www.ruanyifeng.com/blog/2014/05/restful_api.html
- https://www.ruanyifeng.com/blog/2018/10/restful-api-best-practices.html
- https://www.zhihu.com/question/28557115
Express 实现原理
Express 源码
-
ES5 写的
-
异步控制采用的回调方式
-
源码目录结构
. ├── index.js # 入口模块 ├── lib │ ├── application.js # app 模块 │ ├── express.js # 组织导出模块 │ ├── middleware # 内置中间件 │ │ ├── init.js │ │ └── query.js │ ├── request.js # 扩展 req 对象 │ ├── response.js # 扩展 res 对象 │ ├── router # 路由系统 │ │ ├── index.js │ │ ├── layer.js │ │ └── route.js │ ├── utils.js # 工具方法 │ └── view.js # 模板引擎处理 └── package.json
中间件匹配机制
- 我们配置的中间件会从上到下进行匹配
- 如果找不到发送响应的中间件默认执行 404 功能
底层:http 模块
Express框架建立在 node.js 内置的 http 模块上。http 模块生成服务器的原始代码如下。
var http = require("http");
var app = http.createServer(function(request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.end("Hello world!");
});
app.listen(3000, "localhost");
一、基本实现
const express = require('./express')
const app = express()
app.get('/', (req, res) => {
res.end('get /')
})
app.get('/login', (req, res) => {
res.end('get login')
})
app.listen(3000, () => console.log('http://localhost:3000'))
二、抽取 Application 模块
抽离 application.js
const http = require('http')
const url = require('url')
const routes = [
// { path: '', method: '', handler: xxx }
]
function Application () {}
Application.prototype.get = function (path, handler) {
routes.push({
path,
method: 'GET',
handler
})
}
Application.prototype.listen = function (...args) {
const server = http.createServer((req, res) => {
// 请求进来,遍历查找 route,执行 handler
const { pathname } = url.parse(req.url)
const method = req.method.toLocaleLowerCase()
const route = routes.find(route => pathname === route.path && method === route.method.toLocaleLowerCase())
if (!route) {
return res.end('404 Not Found')
}
route.handler(req, res)
})
server.listen(...args)
}
module.exports = Application
express.js
const Application = require('./application')
function createApplication () {
return new Application()
}
module.exports = createApplication
将路由添加到 application 实例上
const http = require('http')
const url = require('url')
function Application () {
this.routes = [
// { path: '', method: '', handler: xxx }
]
}
Application.prototype.get = function (path, handler) {
this.routes.push({
path,
method: 'GET',
handler
})
}
Application.prototype.listen = function (...args) {
const server = http.createServer((req, res) => {
// 请求进来,遍历查找 route,执行 handler
const { pathname } = url.parse(req.url)
const method = req.method.toLocaleLowerCase()
const route = this.routes.find(route => pathname === route.path && method === route.method.toLocaleLowerCase())
if (!route) {
return res.end('404 Not Found')
}
route.handler(req, res)
})
server.listen(...args)
}
module.exports = Application
三、应用和路由的分离
application.js
const http = require('http')
const Router = require('./router')
function Application () {
this._router = new Router()
}
Application.prototype.get = function (path, handler) {
this._router.get(path, handler)
}
Application.prototype.listen = function (...args) {
const server = http.createServer((req, res) => {
// 请求进来,直接交给路由系统处理
this._router.handle(req, res)
})
server.listen(...args)
}
module.exports = Application
router/index.js
const url = require('url')
function Router () {
this.stack = [] // 维护路由表
}
Router.prototype.get = function (path, handler) {
this.stack.push({
path,
method: 'GET',
handler
})
}
Router.prototype.handle = function (req, res) {
// 请求进来,遍历查找 route,执行 handler
const { pathname } = url.parse(req.url)
const method = req.method.toLocaleLowerCase()
const route = this.stack.find(route => pathname === route.path && method === route.method.toLocaleLowerCase())
if (!route) {
return res.end('404 Not Found')
}
route.handler(req, res)
}
module.exports = Router
四、处理不同的请求方法
五、处理不同的请求路径
5.1 使用 path-to-regexp
注意版本问题
const pathRegexp = require('path-to-regexp')
function Layer (path, handler) {
this.path = path
this.handler = handler
this.keys = []
this.regexp = pathRegexp(path, this.keys, {})
this.params = {}
}
Layer.prototype.match = function (pathname) {
const match = this.regexp.exec(pathname)
if (match) {
this.keys.forEach((key, index) => {
this.params[key.name] = match[index + 1]
})
return true
}
return false
}
module.exports = Layer
5.2 处理动态路由路径参数
5.3 提取 Layer 处理模块
const url = require('url')
const methods = require('methods')
const Layer = require('./layer')
function Router () {
this.stack = []
}
methods.forEach(method => {
Router.prototype[method] = function (path, handler) {
const layer = new Layer(path, handler)
layer.method = method
this.stack.push(layer)
}
})
Router.prototype.handle = function (req, res) {
const { pathname } = url.parse(req.url)
const method = req.method.toLowerCase()
const route = this.stack.find(layer => {
// const keys = []
// const regexp = pathRegexp(route.path, keys, {})
const match = layer.match(pathname)
if (match) {
req.params = req.params || {}
Object.assign(req.params, layer.params)
}
return match && layer.method === method
})
if (route) {
return route.handler(req, res)
}
res.end('404 Not Found.')
}
module.exports = Router
六、实现路由中间件功能
实现顶层中间件逻辑
Router.prototype.handle = function (req, res) {
const { pathname } = url.parse(req.url)
const method = req.method.toLowerCase()
let index = 0
const next = () => {
if (index >= this.stack.length) {
return res.end(`Can not get ${pathname}`)
}
const layer = this.stack[index++]
const match = layer.match(pathname)
if (match) {
req.params = req.params || {}
Object.assign(req.params, layer.params)
}
if (match && layer.method === method) {
return layer.handler(req, res, next)
}
next()
}
next()
// const layer = this.stack.find(layer => {
// // const keys = []
// // const regexp = pathRegexp(layer.path, keys, {})
// const match = layer.match(pathname)
// if (match) {
// req.params = req.params || {}
// Object.assign(req.params, layer.params)
// }
// return match && layer.method === method
// })
// if (layer) {
// return layer.handler(req, res)
// }
// res.end('404 Not Found.')
}
实现第二层中间件逻辑
route.js
const methods = require('methods')
const Layer = require('./layer')
function Route () {
this.stack = [
// { path, method, handler }
]
}
// 遍历执行当前路由对象中所有的处理函数
Route.prototype.dispatch = function (req, res, out) {
// 遍历内层的 stack
let index = 0
const method = req.method.toLowerCase()
const next = () => {
if (index >= this.stack.length) return out()
const layer = this.stack[index++]
if (layer.method === method) {
return layer.handler(req, res, next)
}
next()
}
next()
}
methods.forEach(method => {
Route.prototype[method] = function (path, handlers) {
handlers.forEach(handler => {
const layer = new Layer(path, handler)
layer.method = method
this.stack.push(layer)
})
}
})
module.exports = Route
layer.js
const pathRegexp = require('path-to-regexp')
function Layer (path, handler) {
this.path = path
this.handler = handler
this.keys = []
this.regexp = pathRegexp(path, this.keys, {})
this.params = {}
}
Layer.prototype.match = function (pathname) {
const match = this.regexp.exec(pathname)
if (match) {
this.keys.forEach((key, index) => {
this.params[key.name] = match[index + 1]
})
return true
}
return false
}
module.exports = Layer
route/index.js
const url = require('url')
const methods = require('methods')
const Layer = require('./layer')
const Route = require('./route')
function Router () {
this.stack = []
}
methods.forEach(method => {
Router.prototype[method] = function (path, handlers) {
const route = new Route()
const layer = new Layer(path, route.dispatch.bind(route))
layer.route = route
this.stack.push(layer)
route[method](path, handlers)
}
})
Router.prototype.handle = function (req, res) {
const { pathname } = url.parse(req.url)
let index = 0
const next = () => {
if (index >= this.stack.length) {
return res.end(`Can not get ${pathname}`)
}
const layer = this.stack[index++]
const match = layer.match(pathname)
if (match) {
req.params = req.params || {}
Object.assign(req.params, layer.params)
}
// 顶层只判定请求路径,内层判定请求方法
if (match) {
// 顶层这里调用的 handler 其实就是 dispatch 函数
return layer.handler(req, res, next)
}
next()
}
next()
// const layer = this.stack.find(layer => {
// // const keys = []
// // const regexp = pathRegexp(layer.path, keys, {})
// const match = layer.match(pathname)
// if (match) {
// req.params = req.params || {}
// Object.assign(req.params, layer.params)
// }
// return match && layer.method === method
// })
// if (layer) {
// return layer.handler(req, res)
// }
// res.end('404 Not Found.')
}
module.exports = Router
handler 处理
dispatch 调用
七、Express use 的中间件实现
Express 中间件的使用规则
- use 方法
- use 方法的第 1 个路径参数
- / 匹配所有
- /a 匹配以 /a 开头的
- /a/b 匹配完全一致的
- 不提供,默认就是 /
- 中间件的链式调用
- 中间件中的 next 函数
application.js
const http = require('http')
const Router = require('./router')
const methods = require('methods')
function App () {
this._router = new Router()
}
methods.forEach(method => {
App.prototype[method] = function (path, ...handlers) {
this._router[method](path, handlers)
}
})
App.prototype.use = function (path, ...handlers) {
this._router.use(path, handlers)
}
App.prototype.listen = function (...args) {
const server = http.createServer((req, res) => {
this._router.handle(req, res)
})
server.listen(...args)
}
module.exports = App
route/index.js
const url = require('url')
const methods = require('methods')
const Layer = require('./layer')
const Route = require('./route')
const { type } = require('os')
function Router () {
this.stack = []
}
methods.forEach(method => {
Router.prototype[method] = function (path, handlers) {
const route = new Route()
const layer = new Layer(path, route.dispatch.bind(route))
layer.route = route
this.stack.push(layer)
route[method](path, handlers)
}
})
Router.prototype.handle = function (req, res) {
const { pathname } = url.parse(req.url)
let index = 0
const next = () => {
if (index >= this.stack.length) {
return res.end(`Can not get ${pathname}`)
}
const layer = this.stack[index++]
const match = layer.match(pathname)
if (match) {
req.params = req.params || {}
Object.assign(req.params, layer.params)
}
// 顶层只判定请求路径,内层判定请求方法
if (match) {
// 顶层这里调用的 handler 其实就是 dispatch 函数
return layer.handler(req, res, next)
}
next()
}
next()
Router.prototype.use = function (path, handlers) {
if (typeof path === 'function') {
handlers.unshift(path) // 处理函数
path = '/' // 任何路径都以它开头的
}
handlers.forEach(handler => {
const layer = new Layer(path, handler)
layer.isUseMiddleware = true
this.stack.push(layer)
})
}
module.exports = Router
route/layer.js
const pathRegexp = require('path-to-regexp')
function Layer (path, handler) {
this.path = path
this.handler = handler
this.keys = []
this.regexp = pathRegexp(path, this.keys, {})
this.params = {}
}
Layer.prototype.match = function (pathname) {
const match = this.regexp.exec(pathname)
if (match) {
this.keys.forEach((key, index) => {
this.params[key.name] = match[index + 1]
})
return true
}
// 匹配 use 中间件的路径处理
if (this.isUseMiddleware) {
if (this.path === '/') {
return true
}
if (pathname.startsWith(`${this.path}/`)) {
return true
}
}
return false
}
module.exports = Layer