由淺入深理解express源碼(一)

準備

項目以 mocha + chai + supertest 測試驅動開發,閱讀者須要儲備的知識有:javascript

一、mocha做爲測試框架在項目中的運用 mochajs.orghtml

二、chai斷言庫的api使用 www.chaijs.comjava

三、使用supertest驅動服務器的啓動,並模擬訪問服務器地址。npm.taobao.org/package/sup…node

四、node http服務端和客戶端的基本知識 nodejs.cn/api/http.ht…express

實現目標

本系列項目和文章的目標是一步一步實現一個簡化版的express,這就須要將express的源碼進行一步一步的剝離。迭代一的目標是實現服務器的啓動、app 對象get方法的簡化版本以及項目基本結構的肯定。對於get方法只實現到經過path找到對應的callback。path不作分解和匹配npm

項目結構

express1
  |
  |-- lib
  |    | 
  |    |-- express.js //負責實例化application對象
  |    |-- application.js //包裹app層
  |
  |-- examples
  |    |-- index.js // express1 實現的使用例子
  |
  |-- test
  |    |
  |    |-- index.js // 自動測試examples的正確性
  |
  |-- index.js //框架入口
  |-- package.json // node配置文件

複製代碼

執行流程

test/index.js 主要是集成mocha + chai + supertest 自動發送examples/index.js中註冊的get請求。json

examples/index.js 主要是對lib文件下的兩個源碼功能實現的驗證。設計模式

lib/express.js 對application中的對象進行初始化,完成createServer方法的callback。api

lib/application.js 一期迭代的主要功能和實現。主要實現了listen接口和get兩個對外接口和handle供express.js 使用數組

代碼解析

首先看看lib/application.js,代碼中有_init, _defaultConfiguration, _set, handle, listen, get幾個方法:

_init: 初始化app對象須要的一些基礎設置

_defaultConfiguration: 設置環境變量env,後期迭代預留

_set: 對app中setting對象的操做,爲後期迭代預留

handle: http.createServer 中的回調函數最終執行,遍歷paths,肯定調用哪一個get函數中的回調函數

listen: 啓動http服務。靈活使用arguments將http服務中listen方法的參數留給用戶自行配置。同時createServer方法傳入this,將在express.js中定義定app方法做爲服務請求的回調函數。

get: 實現app的get接口,主要是對全部的get請求進行註冊,存入app對象的paths數組中,方便handle中實現精準回調。

源碼:

'use strict'
/** * 採用的是設計模式中的模塊模式,定義app對象,爲其掛載方法 */
const http = require('http')
let app = exports = module.exports = {}
/** * 初始化app對象須要的一些基礎設置 * paths: 存放全部使用get方法註冊的請求,單體對象的格式爲: * { * pathURL 請求的地址 cb 請求對應的回調函數 * } */
app._init = function init() {
  this.setting = {}
  this.paths = []
  this.defaultConfiguration()
}
/** * 設置環境變量env,後期迭代預留 */
app._defaultConfiguration = function defaultConfiguration() {
  let env = process.env.NODE_ENV || 'development'
  this.set('env', env)
  this.set('jsonp callback name', 'callback')
}
/** * 對app中setting對象的操做,爲後期迭代預留 */
app._set = function set(key, val) {
  if (arguments.length === 1) {
    this.setting[key]
  }
  this.setting[key] = val
}
/** * http.createServer 中的回調函數最終執行,遍歷paths,肯定調用哪一個get函數中的回調函數 */
app.handle = function handle(req, res) {
  let pathURL = req.url
  for (let path of this.paths) {
    if (pathURL === path.pathURL) {
      path.cb(req, res)
    }
  }
}
/** * 啓動http服務 */
app.listen = function listen() {
  let server = http.createServer(this)
  return server
    .listen
    .apply(server, arguments)
}
/** * 實現app的get接口,主要是對全部的get請求進行註冊,方便handle中實現精準回調 */
app.get = function get(path, cb) {
  let pathObj = {
    pathURL: path,
    cb: cb
  }
  this
    .paths
    .push(pathObj)
}
複製代碼

exammple/index.js 啓動服務,若是根據訪問地址的不一樣,給出不一樣的輸出

const express = require('../index.js')
const app = express()
app.listen(3000) // 啓動端口爲3000的服務
// localhost:3000/path 時調用
app.get('/path', function (req, res) {
  console.log('visite /path , send : path')
  res.end('path')
})
// localhost:3000/ 時調用
app.get('/', function (req, res) {
  console.log('visite /, send: root')
  res.end('root')
})

exports = module.exports = app
複製代碼

test/index.js 測試exapmles中的代碼,驗證是否按照地址的不一樣,進了不一樣的回調函數

'use strict'

const assert = require('chai').assert

const app = require('../examples/index.js')
const request = require('supertest')(app)
describe('服務器測試', () => {
  // 若是走的不是examples中的get:/ 測試不經過
  it('GET /', (done) => {
    request
      .get('/')
      .expect(200)
      .end((err, res) => {
        if (err) 
          return done(err)
        assert.equal(res.text, 'root', 'res is wrong') // 根據response調用end方法時的輸出爲: root
        done()
      })
  })
  // 若是走的不是examples中的get:/path 測試不經過
  it('GET /path', (done) => {
    request
      .get('/path')
      .expect(200)
      .end((err, res) => {
        if (err) 
          return done(err)
        assert.equal(res.text, 'path', 'res is wrong') // 根據response調用end方法時的輸出爲: path
        done()
      })
  })
})


複製代碼

test測試結果以下:

下期預告

先作個簡單的嘗試,下一期咱們實現app的get,post等方法,主要是http中的methods,以及簡單的路由處理。

由淺入深理解express源碼 (二)

相關文章
相關標籤/搜索