Express 簡單實現

今天咱們利用 node 中的 http 模塊實現一個 express 框架。關於 http 模塊的使用你們可自行查閱相關文檔javascript

1、實現 express 啓動服務

原生 express 啓動服務html

let express = require('express')
let app = express()
let server = app.listen(3000, 'localhost', function () {
	console.log(`app is listening at http://${server.address().address}:${server.address().port}`)
})
複製代碼

若實現如上功能,首先咱們肯定的是 express 是一個函數,執行後返回一個 app 函數,且有 listen 方法,咱們不如稱這個 app 函數爲監聽函數。實現代碼以下:java

let http = require('http')
function createApplication () {
  // app是一個監聽函數
  let app = function (req, res) {
    res.end('hello world')
  }
  app.listen = function () {
    let server = http.createServer(app)
    // arguments就是參數(3000, 'localhost', function () {})
    server.listen(...arguments)
    return server
  }
  return app
}
module.exports = createApplication
複製代碼

2、實現 express 路由

原生 express 使用路由node

let express = require('express')
let app = express()
app.get('/name', function (req, res) {
  res.end('get name')
})
app.listen(3000, 'localhost', function () {
  console.log('app is listening')
})
複製代碼

若實現如上功能,咱們的 app 監聽函數須要實現一個 get 方法,該方法能夠把本次調用存儲在 app 的路由數組中,當服務啓動成功後,監聽到匹配的路由時便可調用對應的回調。實現代碼以下:git

let http = require('http')
function createApplication () {
  // app是一個監聽函數
  let app = function (req, res) {
    // 當前請求方法
    let m = req.method.toLocaleLowerCase()
    // 當前請求路徑
    let { pathname } = url.parse(req.url, true)
    for (let i = 0; i < app.routes.length; i++) {
      let { path, method, handler } = app.routes[i]
      // 若是該路由項匹配到當前請求,則執行回調
      if (path === pathname && method === m) {
        handler(req, res)
      }
    }
  }
  // 存儲全部的請求,以便監聽函數調用
  app.routes = []
  app.get = function (path, handler) {
    let layer = {
      path,
      method: 'get',
      handler
    }
    app.routes.push(layer)
  }
  // app.listen = function () {}
  return app
}
module.exports = createApplication
複製代碼

其餘 RESTFUL 方法同理。實現代碼以下:github

let http = require('http')
function createApplication () {
  // app是一個監聽函數
  let app = function (req, res) {
    // 當前請求方法
    let m = req.method.toLocaleLowerCase()
    // 當前請求路徑
    let { pathname } = url.parse(req.url, true)
    for (let i = 0; i < app.routes.length; i++) {
      let { path, method, handler } = app.routes[i]
      // 若是該路由項匹配到當前請求,則執行回調
      if ((path === pathname || path === '*') && (method === m || method === 'all')) {
        handler(req, res)
      }
    }
  }
  // 存儲全部的請求,以便監聽函數調用
  app.routes = []
  // http.METHODS 獲取RESTFUL全部方法
  http.METHODS.forEach(method => {
    method = method.toLocaleLowerCase()
    app[method] = function (path, handler) {
      let layer = {
        method,
        path,
        handler
      }
      app.routes.push(layer)
    }
  })
  // 若是沒有匹配成功,最終執行all函數所存儲的回調
  app.all = function (path, handler) {
    let layer = {
      method: 'all', // 表示所有匹配
      path,
      handler
    }
    app.routes.push(layer)
  }
  // app.listen = function () {}
  return app
}
module.exports = createApplication
複製代碼

3、實現 express 中間件

原生 express 使用中間件express

let express = require('express')
let app = express()
app.use(function (req, res, next) {
  res.setHeader('Content-Type', 'text/html; charset=utf-8')
  next()
})
app.use('/name', function (req, res, next) {
  next()
})
app.get('/name', function (req, res) {
  res.end('獲取姓名')
})
app.listen(3000, 'localhost', function () {
  console.log('app is listening')
})
複製代碼

因而可知,use 方法和 method 方法大同小異,重點是實現 next 方法。 next 函數的做用便是在請求到達前更改一些上下文環境,好比修改返回字符集編碼等,且按順序執行,固可用迭代的方式實現。實現代碼以下:api

let http = require('http')
let url = require('url')
function createApplication () {
  // app是一個監聽函數
  let app = function (req, res) {
    // 當前請求方法
    let m = req.method.toLocaleLowerCase()
    // 當前請求路徑
    let { pathname } = url.parse(req.url, true)
    // 迭代次數索引
    let index = 0
    // 用next代替for循環
    function next () {
      // 若是所有路由數組都不知足,則返回找不到
      if (index === app.routes.length) {
        return res.end(`can not ${m} ${pathname}`)
      }
      // 處理中間件
      let { method, path, handler } = app.routes[index++]
      if (method === 'middle') {
        // 若是該中間件path是/,匹配所有請求,執行回調;若是相等,執行回調;若是該中間件path被包含在當前請求url中,也執行回調
        if (path === '/' || path === pathname || pathname.startsWith(path + '/')) {
          handler(req, res, next)
        } else {
          next()
        }
      } else {
        if ((path === pathname || path === '*') && (method === m || method === 'all')) {
          handler(req, res, next)
        } else {
          next()
        }
      }
    }
    // 直接調用next函數,根據路徑匹配查找對應回調並執行
    next()
  }
  // app.routes = []
  // http.METHODS.forEach(() => {}
  // app.all = function (path, handler) {}
  // 中間件:參數能夠傳path,也能夠不傳,默認'/'
  app.use = function (path, handler) {
    if (typeof handler !== 'function') {
      handler = path
      path = '/'
    }
    let layer = {
      method: 'middle',
      path,
      handler
    }
    app.routes.push(layer)
  }
  // app.listen = function () {}
  return app
}
module.exports = createApplication
複製代碼

此時,express 的主要功能已經實現,下面來看下若是執行錯誤經過 next 函數參數進行返回的狀況。 若是 next 函數有參數,會跳過接下來的全部中間件和路由,直接返回錯誤參數消息,因此在處理中間件以前要先判斷錯誤狀況,而且將錯誤繼續向下傳遞,只有匹配到有四個參數的回調時才執行。實現代碼以下:數組

let http = require('http')
function createApplication () {
  // app是一個監聽函數
  let app = function (req, res) {
    // 此處省略...
    function next (err) {
      // 此處省略...
      if (err) {
        res.end(err)
        if (handler.length === 4) {
          handler(err, req, res, next)
        } else {
          next(err)
        }
      } else {
        // 處理中間件,此處省略...
      }
    }
    next()
  }
  // app.routes = []
  // http.METHODS.forEach(() => {}
  // app.all = function (path, handler) {}
  // app.use = function (path, handler) {}
  // app.listen = function () {}
  return app
}
module.exports = createApplication
複製代碼

源碼app

好了,到這裏所有代碼已經給出,就到此爲止吧~ 😋😋😋

相關文章
相關標籤/搜索