express简易版实现

初始化

function application() {
  const app = function (req, res) {}

  // 存放组件间和路由函数
  app.routes = []
  // 监听端口号和绑定请求函数
  app.listen = function () {
    let server = http.createServer(app)
    server.listen(...arguments)
  }

  return app;
}

module.exports = application

中间件

app.use = function (path, handler) {
    // 如果只有一个参数,将path和handler重新赋值
    if (typeof handler === "undefined") {
        handler = path
        path = "/"
    }
    let layer = {
        method: "middle",
        path,
        callback: handler,
    }
    // 存进数组等待调用
    app.routes.push(layer)
}

路由函数

  // 常见的路由方法都要添加到app.routes中
  const methods = ["get", "post", "delete", "put"];
  // all 可以监听所有函数
  ;[...methods, "all"].forEach((method) => {
    // 将已知的http方法加入到app.routes中
    app[method] = function (path, callback) {
      // 判断如果是带参数的路由,截取参数名称和转换路径
      if (path.includes(":")) {
        let params = []
        let regexp = path.replace(/:([^\/]+)/g, function () {
          params.push(arguments[1])
          return "([^/]+)"
        })
        // 路径转成正则,会跟request的url路径比较,匹配参数value值
        path = new RegExp(regexp)
        // 绑定参数到路径上
        path.params = params
      }
      let layer = {
        method,
        path,
        callback,
      }
      app.routes.push(layer)
    }
  })

app函数

在初始化的时候,已经将app作为http.createServer(app)的函数回调,所以可以拿到requestresponse两个参数,接下来还要对pathnamemethodapp.routes数组里面的对象进行匹配,next函数负责取出app.routes数组里面的对象进行判断,如果不合适就继续执行下一个,直到结束:

  • 取出req.urlmethod判断
  • next方法逐个取出app.routes
  • app.routes取完仍然没有匹配上,抛错
  • error参数,到中间件->判断中间件是否存在四个参数
  • 分别中间件函数和普通路由函数
 const app = function (req, res) {
    const { pathname, query } = url.parse(req.url, true)
    const method = req.method.toLowerCase()
    req.query = query 
    req.path = pathname

    let index = 0
    // next 函数
    function next(error) {
        // 取出第一项,后面index累加
      let layer = app.routes[index++]
      // 如果不存在 抛出错误
      if (!layer) return res.end(error || `Not Found ${req.method} ${req.url}`)
      // 如果有错误信息 查找是否有中间件函数,并且参数要有4个,error,req,res,next
      if (error) {
        if (layer.method === "middle" && layer.callback.length === 4) {
          return layer.callback(error, req, res, next)
        } else {
          next(error)
        }
      }
      // 中间件函数
      if (layer.method === "middle" && layer.callback.length <= 3) {
        if (
          layer.path === "/" ||
          layer.path === pathname ||
          pathname.startsWith(layer.path + "/")
        ) {
            // 执行函数
          return layer.callback(req, res, next)
        } else {
          next()
        }
      } else {
        // 如果有params,说明url路径有参数
        let params = layer.path.params
        if (params) {
            // 判断路径是否能匹配上正则
          if (layer.path.test(pathname) && method === layer.method) {
            let [, ...values] = pathname.match(layer.path) || []
            // 取出value值绑定到params
            params = params.reduce(
              (memo, current, index) => ((memo[current] = values[index]), memo),
              {}
            )
            res.params = params
            // 执行函数
            return layer.callback(req, res)
          }
        }

        // 正常的路由函数,匹配到就执行方法
        if (
          (pathname === layer.path || layer.path === "*") &&
          (method === layer.method || layer.method === "all")
        ) {
          return layer.callback(req, res)
        } else {
          next()
        }
      }
    }
    next(0)
  }

request 和 response 添加常用方法

send

根据参数类型返回对应的格式

res.send = function (value = "") {
    if (typeof value === "string" || Buffer.isBuffer(value)) {
    res.end(value)
    }
    if (typeof value === "object") {
    res.end(JSON.stringify(value))
    }
    if (typeof value === "number") {
    res.statusCode = value
    let str = httpServer.STATUS_CODES[value + ""]
    res.end(str)
    }
}

sendFile

发送文件类型

res.sendFile = function (filepath, options) {
    let root = options.root || __dirname
    let abspath = path.join(root, filepath)
    fs.createReadStream(abspath).pipe(res)
    return
}

render

渲染模板,需要安装ejs

res.render = function (name, data) {
    try{
        // 默认取views下以html结尾的文件
        let filepath = path.resolve(__dirname, "./views/" + name + ".html")
        let str = fs.readFileSync(filepath, "utf-8")
        str = ejs.render(str, data)
        res.setHeader("Content-type", "text/html;charset=utf-8")
        res.end(str)
        return
    }catch(error){
        // 最好用中间件处理,next()
        res.send(error);
    }
}

static

静态服务,这里做的比较简单,可以添加上缓存和gzip这些响应头处理

application.static = function (dirname) {
  return function (req, res, next) {
    let { pathname } = url.parse(req.url, true)
    pathname = path.join(dirname, pathname)
    fs.stat(pathname, (err, statObj) => {
      if (err) {
        next();
        return;
      }
      if (statObj.isFile()) {
        fs.createReadStream(pathname).pipe(res)
        return
      } else {
        return next()
      }
    })
  }
}