今天我們利用 node
中的 http
模組實現一個 express
框架。關於 http
模組的使用大家可自行查閱相關文件。
一、實現 express 啟動服務
原生 express
啟動服務
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
函式為監聽函式。實現程式碼如下:
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
複製程式碼
二、實現 express 路由
原生 express
使用路由
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
的路由陣列中,當服務啟動成功後,監聽到匹配的路由時即可呼叫對應的回撥。實現程式碼如下:
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
方法同理。實現程式碼如下:
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
複製程式碼
三、實現 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
函式的作用即是在請求到達前更改一些上下文環境,比如修改返回字符集編碼等,且按順序執行,固可用迭代的方式實現。實現程式碼如下:
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
複製程式碼
好了,到這裡全部程式碼已經給出,就到此為止吧~ ???