准備
項目以 mocha + chai + supertest 測試驅動開發,閱讀者需要儲備的知識有:
1、mocha作為測試框架在項目中的運用 mochajs.org
2、chai斷言庫的api使用 www.chaijs.com
3、使用supertest驅動服務器的啟動,並模擬訪問服務器地址。npm.taobao.org/package/sup…
4、node http服務端和客戶端的基本知識 nodejs.cn/api/http.ht…
實現目標
gitee: gitee.com/kaisela/mye…
本系列項目和文章的目標是一步一步實現一個簡化版的express,這就需要將express的源碼進行一步一步的剝離。迭代一的目標是實現服務器的啟動、app 對象get方法的簡化版本以及項目基本結構的確定。對於get方法只實現到通過path找到對應的callback。path不做分解和匹配
項目結構
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請求。
examples/index.js 主要是對lib文件下的兩個源碼功能實現的驗證。
lib/express.js 對application中的對象進行初始化,完成createServer方法的callback。
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,以及簡單的路由處理。