# NodeJs 實戰——原生 NodeJS 輕仿 Express 框架從需求到實現(一)
這篇文章是一個系列的文章的第一篇,主要是自己實現一個express的簡易版框架,加深對nodejs的理解。
確認需求
我們從一個經典的 Hello World 開始,這是 Express 官方文件的第一個例項, 程式碼如下
const express = require('express'); const app = express(); app.get('/', (req, res) => res.send('Hello World!')); app.listen(3000, () => console.log('Example app listening on port 3000!')); 複製程式碼
執行 helloworld.js
node helloworld.js 複製程式碼
在瀏覽器上開啟 ofollow,noindex">http://localhost:3000 ,網頁將顯示 Hello World。
程式碼實現
由 Hello World 例項分析,我們可以看出 express 返回了一個函式,而執行這個函式會返回一個類的例項,例項具有 get 和 listen 兩個方法。
第一步,建立目錄
首先我們先構建下圖的目錄結構

第二步,建立入口檔案
其中,入口為 express.js 檔案,入口非常簡單
const Application = require('./application'); function express() { return new Application(); } module.exports = express; 複製程式碼
第三步,實現應用程式類 Application
應用程式類為 application.js 檔案,在這次實現中我們要達到如下要求:
- 實現 http 伺服器
- 實現 get 路由請求
- 實現 http 伺服器非常簡單,我們可以參考 nodejs 官網的實現。
const http = require('http'); const hostname = '127.0.0.1'; const port = 3000; const server = http.createServer((req, res) => { res.statusCode = 200; res.setHeader('Content-Type', 'text/plain'); res.end('Hello World\n'); }); server.listen(port, hostname, () => { console.log(`Server running at http://${hostname}:${port}/`); }); 複製程式碼
參考該案例,實現 express 的 listen 函式。
listen: function (port, cb) { var server = http.createServer(function(req, res) { console.log('http.createServer...'); }); return server.listen(port, cb); } 複製程式碼
當前 listen 函式包含了兩個引數,但是 http.listen 裡包含了許多過載函式,為了和 http.listen 一致,可以將函式設定為 http.listen 的代理,這樣可以保持 express 的 listen 函式和 http.listen 的引數保持一致。
listen: function (port, cb) { var server = http.createServer(function(req, res) { console.log('http.createServer...'); }); return server.listen.apply(server, arguments); } 複製程式碼
nodejs 後臺伺服器程式碼根據 http 請求的不同,繫結不同的邏輯。在 http 請求到伺服器後,伺服器根據一定的規則匹配這些 http 請求,執行與之相對應的邏輯,這個過程就是 web 伺服器基本的執行流程。
對於這些 http 請求的管理,我們稱之為—— 路由管理 ,每個 http 請求就預設為一個 路由 。 我們建立一個 router 陣列用來管理所有路由對映,參考 express 框架,抽象出每個路由的基本屬性:
- path 請求路徑,例如:/goods。
- method 請求方法,例如:GET、POST、PUT、DELETE。
- handle 處理函式
var router = [ { path: '*', method: '*', handle: function(req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); } } ]; 複製程式碼
修改 listen 方法,將 http 請求攔截邏輯修改為匹配 router 路由表,迴圈 router 數組裡的物件,當請求方法和路徑一致時,執行回撥函式 handler 方法。
listen: function(port, cb) { const server = http.createServer(function(req, res) { // 迴圈請求過來放入router陣列的物件,當請求方法和路勁與物件一致時,執行回撥handler方法 for (var i = 1, len = router.length; i < len; i++) { if ( (req.url === router[i].path || router[i].path === '*') && (req.method === router[i].method || router[i].method === '*') ) { return router[i].handle && router[i].handle(req, res); } } return router[0].handle && router[0].handle(req, res); }); return server.listen.apply(server, arguments); } 複製程式碼
實現 get 路由請求非常簡單,該函式主要是新增 get 請求路由。
get: function(path, fn) { router.push({ path: path, method: 'get', handle: fn }) } 複製程式碼
完整的程式碼如下:
const http = require('http'); const url = require('url'); // 應用程式類 function Application () { // 用來儲存路由的陣列 this.router = [[ path: '*', method: '*', handler: function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('404'); } ]]; } // 在Application的原型上拓展get方法,以便Application的例項具有該方法。 Application.prototype.get = function (path, handler) { // 將請求路由壓入棧內 this.router.push({ path, method: 'get', handler }); } // 在Application的原型上拓展listen方法,以便Application的例項具有該方法。 Application.prototype.listen = function () { const server = http.createServer(function(req, res) { if(!res.send) { // 拓展res的方法,讓其支援send方法 res.send = function (body) { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end(body); } } // 迴圈請求過來放入router陣列的物件,當請求方法和路勁與物件一致時,執行回撥handler方法 for (var i = 1, len = router.length; i < len; i++) { if ( (req.url === router[i].path || router[i].path === '*') && (req.method === router[i].method || router[i].method === '*') ) { return router[i].handle && router[i].handle(req, res); } } return router[0].handle && router[0].handle(req, res); }); return server.listen.apply(server, arguments); }) } 複製程式碼
總結
我們這裡主要實現了express簡單的搭建伺服器和get請求方法的功能,滿足了Hello World這個簡單例項的要求。