Express

12 篇文章 1 订阅

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 接口设计规范

参考链接

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值