# Express

# express的基本使用

const express = require("express")

const app = express()

app.get("/",function(req,res)  {
  res.end("home")
})

app.get("/login",function(req,res)  {
  res.end("login")
})

// 匹配所有,上面的路由都匹配不到,就会走到这里
// app.all("*",function(req,res)  {
//   res.end("404")
// })

app.listen(3000)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

express 内部采用了回调的形式实现,内置了中间件、路由系统、模板引擎、静态服务。

上述代码中,使用express启动了一个服务,从使用的方式来看,express是一个函数,app是这个函数返回的对象,express内置了路由系统,get方法接收两个参数,一个是路径path,一个是回调函数,回调函数中接收两个参数。req, res。里面还有一个listen方法,监听的端口号,同样的也可以接收一个回调函数,在监听服务启动成功时执行。

# express的基本实现

# 第一个版本

express的源码目录相对来说比较简单,它的package.json文件中没有标明入口文件,这样会查找当前目录下的index.js。

/*!
 * express
 * Copyright(c) 2009-2013 TJ Holowaychuk
 * Copyright(c) 2013 Roman Shtylman
 * Copyright(c) 2014-2015 Douglas Christopher Wilson
 * MIT Licensed
 */

'use strict';

module.exports = require('./lib/express');
1
2
3
4
5
6
7
8
9
10
11

index.js中就做了一件事情,导出了lib模块下的express.js文件。内部导出了一个函数createApplication

const http = require("http")
const url = require("url")
// 声明一个stack用于存放路由信息
let routes = [
  {
    // 默认规则
    path: "*",
    method: "all",
    handler(req, res) {
      res.end(`cannot  found ${req.method}  ${req.url}`)
    },
  },
]
function createApplication() {
  return {
    // 每次执行get方法的时候,会向栈中新注册一个路由
    get(path, handler) {
      routes.push({
        path,
        method: "get",
        handler,
      })
    },
    // 执行listen方法的时候
    listen(...args) {
      console.log("args",args) // [ 3000, [Function (anonymous)] ]
      // 使用原生的http模块启动一个服务
      const server = http.createServer((req, res) => {
        // 解析请求的路径
        let { pathname } = url.parse(req.url)
        // 拿到请求的方法
        let requestMethod = req.method.toLocaleLowerCase()
        // 从第一项开始匹配
        for (let i = 1; i < routes.length; i++) {
          // 将每一项对应的元素解构出来
          let { path, method, handler } = routes[i]
          // 路由的匹配需要注意一点,路径和方法全部匹配到才算匹配到
          if (path === pathname && requestMethod === method) {
            // 匹配到了就执行回调
            return handler(req, res)
          }
        }
        // 都匹配不到走第一个
        routes[0].handler(req, res)
      })

      server.listen(...args)
    },
  }
}
module.exports = createApplication
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# 第二个版本

上述代码中,我们把创建服务的逻辑,路由匹配的逻辑,全部都放在一个文件总实现,这样显然不够优雅,我们想到将服务的创建和路由的匹配进行封装。各司其职。为此我们创建一个application.js来封装应用创建的逻辑。新建一个router文件夹,用于处理路由的逻辑。

// application.js
const http = require("http")
const Router = require("./router")

function Application() {
  this._router = new Router()
}
// 将原本的get方法放在原型上面实现
Application.prototype.get = function (path, handler) {
  // 内部调用router的get方法,处理get请求的路由处理,后续还有post等方法。
  this._router.get(path, handler)
}

// 将原本的listen方法放在原型上实现
Application.prototype.listen = function (...args) {
  const server = http.createServer((req, res) => {
    // 所有的路径都没有匹配到的时候,就执行这个方法
    function done() {
      res.end(`cannot ${req.method} ${req.url}`)
    }
    // 调用router中封装的handle方法,处理请求响应
    this._router.handle(req, res,done)
  })
  server.listen(...args)
}

module.exports = Application
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

我们来看看router的内部实现:

const url = require("url")

function Router() {
  this.stack = []
}
/**
 * 这个方法提供出来的主要能力是操作自己的栈
 */
Router.prototype.get = function (path, handler) {
  this.stack.push({
    path,
    handler,
    method: "get",
  })
}

Router.prototype.handle = function (req, res, done) {
  let { pathname } = url.parse(req.url)
  let requestMethod = req.method.toLocaleLowerCase()
  // 从第一项开始匹配
  for (let i = 0; i < this.stack.length; i++) {
    // 将每一项对应的元素解构出来
    let { path, method, handler } = this.stack[i]
    // 路由的匹配需要注意一点,路径和方法全部匹配到才算匹配到
    if (path === pathname && requestMethod === method) {
      return handler(req, res)
    }
  }
  // 如果路由系统处理不了,就交给应用系统来处理
  done()
}

module.exports = Router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

从上面的代码中可以看到,router的实现也是基于原型的,这里实现了get和handle两个方法,分别处理路由匹配和路径的处理。如果路由系统处理不了,就交给应用系统来处理。

# express的结构化拆分

最后更新时间: 12/21/2023, 10:03:35 PM