寫一個最簡陋的node框架

咱們知道最原生的處理http請求的服務端應該這樣寫node

const http = require("http")
const server = http.createServer(function (req,res) {
    console.log(req)
    res.setHeader("Content-Type","text/plain")
    res.write("hello world")
    console.log(res)
    res.end
})
server.listen(3000)
複製代碼

而後保存爲test.js,用 node --inspect test.js 運行,在chrome://inspect/#devices打開調試界面調試界面,而後訪問 http://localhost:3000/aa/bb?qq=ww ,在調試界面查看結果.chrome

這應該就是最簡單的node http server端的代碼了。 咱們首先建立了一個server,並給他傳入了一個回調函數,而後讓他監聽3000 端口。express

這個回調函數接受兩個參數,req:包含http請求的相關信息;res:即將返回的http相應的相關信息。json

當咱們接收到以個http請求後,咱們最關注哪些信息? 通常比較關注的有:數組

  • 請求的方法
  • 請求的路徑
  • header
  • cookie

等信息。可是這些信息在req中太原始了,瀏覽器

  • 路徑和查詢字符串交叉在一塊兒 req.url:"/aa/bb?qq=ww"
  • cookie藏得更深在 req.headers.cookie

因此咱們在接收一個請求可先作一些處理,好比說先將查詢字符串和cookie從字符串parse爲鍵值對,而後再進入業務邏輯。 咱們能夠這樣寫:bash

const http = require("http")

const server = http.createServer(function (req,res) {
    getQueryObj(req)
    getCookieObj(req)
    res.setHeader("Content-Type","text/plain")
    res.write(JSON.stringify(req.query))
    res.write(JSON.stringify(req.cookie))
    res.end()
})
server.listen(3000)

function getQueryObj(req){
    let query = {}
    let queryString = req.url.split("?")[1] || ""
    let items = queryString.length ? queryString.split("&") : []
    for (let i=0; i < items.length; i++){
        let item = items[i].split("=");
        let name = decodeURIComponent(item[0]);
        let value = decodeURIComponent(item[1]);
        query[name] = value;
    }
    req.query = query
}
function getCookieObj(req) {
    let cookieString = req.headers.cookie || ""
    let cookieObj = {}
    let items = cookieString.length ? cookieString.split(";") : []
    for (let i=0; i < items.length; i++){
        let item = items[i].split("=");
        let name = decodeURIComponent(item[0]);
        let value = decodeURIComponent(item[1]);
        cookieObj[name] = value;
    }
    req.cookie = cookieObj
}
複製代碼

(個人localhost:3000以前設置過cookie,你的瀏覽器未必有,不過沒有也沒有關係,仍是能夠看到查詢字符串) 後面兩個將字符串轉化爲鍵值對的函數很簡單,就很少介紹了。

咱們看到,咱們確實將查詢字符串和cookie提取出來了,已備後續使用。cookie

上述代碼確實完成了任務,可是有很是明顯的缺陷---代碼耦合度過高,不便於維護。此次寫個函數處理查詢字符串,下次寫個函數處理cookie,那再下次呢。app

每添加一個函數就要修改callback,很是不便於維護。函數

那麼咱們能夠怎樣修改呢?

咱們能夠聲明一個函數數組afterReqArrayFuns,而後在callback函數中寫道

afterReqArrayFuns.forEach(fun => {         
    fun(req)
})
複製代碼

這樣能夠代碼自動適應變化,咱們每寫一個函數,就將他push到這個數組,就能夠了。

這是代碼:

const http = require("http")

const myHttp = {
    listen:function(port){               
        const server = http.createServer(this.getCallbackFun())
        return server.listen(port)
    },
    getCallbackFun:function(){
        let that = this
        return function (req,res) {
            that.afterReqArrayFuns.forEach(fun => {        
                fun(req)
            })
            res.write(JSON.stringify(req.query))
            res.end()
        }
    },

    afterReqArrayFuns:[],
    afterReq:function(fun){
        this.afterReqArrayFuns.push(fun)
    }
}

function getQueryObj(req){
    //同上
}
function getCookieObj(req) {
    //同上
}

myHttp.afterReq(getQueryObj)
myHttp.afterReq(getCookieObj)

myHttp.listen(3003)
複製代碼

router

除了預處理http請求,咱們另外一個要求就是對不一樣的請求url作出正確的迴應,在express中,這種寫法很舒服:

const express = require('express');
const app = express();

app.get('/', function (req, res) {
  res.send('Hello World!');
});

app.post('/aa', function (req, res) {
  res.send('Hello World!');
});

app.listen(3000, function () {
  console.log('Example app listening on port 3000!');
});
複製代碼

每一個url對應一個路由函數

可是在原生的http中,咱們可能要寫無數個if-else 或則 switch-case. 那麼咱們怎麼實現相似express的寫法呢?

咱們能夠創建一個url-callback的map對象,每次匹配到相應的url,變調用相應的回調函數。

看代碼

const http = require("http")

const myHttp = {
    listen:function(port){                
        const server = http.createServer(this.getCallbackFun())
        return server.listen(port)
    },
    getCallbackFun:function(){
        let that = this
        return function (req,res) {
            that.afterReqArrayFuns.forEach(fun => {     
                fun(req)
            })
            let path = req.url.split("?")[0]    
            let callback = that.router[req.method][path] || 1   // !!!! look here !!!!!!
            callback(req,res)
            res.end()
        }
    },

    afterReqArrayFuns:[],
    afterReq:function(fun){
        this.afterReqArrayFuns.push(fun)
    },

    router:{
        "GET":{},
        "POST":{},
    },
    get:function (path,callback) {
        this.router["GET"][path] = callback
    },
    post:function(path,callback){
        this.router["POST"][path] = callback
    }
}


myHttp.get("/",(req,res) => {
    res.write("it is /")
})
myHttp.get("/aa/bb",(req,res) => {
    res.setHeader("Content-Type","text/plain")
    res.write("it is /aa/bb")
})

myHttp.listen(3003)
複製代碼

業務邏輯中,callback函數並無寫死,而是動態肯定的

每次寫下myHttp.get(path,callback)後,都會在myHttp.router的創建鍵值對, 而在接受http請求後,模塊會查找對應的路由函數來處理請求。

用ES6 改寫

上面的代碼看起來不規範,咱們用ES6語法來改寫

module.js

const http = require("http");

class fishHttp {
    constructor(){
        this.afterReqArrayFuns = [];
        this.router = {
            "GET":{},
            "POST":{},
        }
    }

    listen(port){       
        const server = http.createServer(this.getCallbackFun());
        return server.listen(port)
    }
    getCallbackFun(req,res){
        let that =this;
        return function (req,res) {
            that.afterReqArrayFuns.forEach(fun => {       
                fun(req)
            });
            res.write(JSON.stringify(req.query));
            let path = req.url.split("?")[0];
            let callback = that.router[req.method][path] || that.NotExistUrl;
            callback(req,res);
        }
    }

    afterReq(fun){
        for(let i = 0;i<arguments.length;i++){
            this.afterReqArrayFuns.push(arguments[i])
        }
    }

    get(path,callback) {
        this.router["GET"][path] = callback
    }
    post(path,callback){
        this.router["POST"][path] = callback
    }

    NotExistUrl(req,res){
        res.end('Not found')
    }
}

module.exports = fishHttp;
複製代碼

在同級目錄下test.js

const fishHttp = require("./module")    //node 自動嘗試.js .node .json擴展名

function getQueryObj(req){
    let query = {}
    let queryString = req.url.split("?")[1] || ""   
    let items = queryString.length ? queryString.split("&") : []
    for (let i=0; i < items.length; i++){
        let item = items[i].split("=");
        let name = decodeURIComponent(item[0]);
        let value = decodeURIComponent(item[1]);
        query[name] = value;
    }
    req.query = query
}
function getCookieObj(req) {
    let cookieString = req.headers.cookie || ""
    let cookieObj = {}
    let items = cookieString.length ? cookieString.split(";") : []
    for (let i=0; i < items.length; i++){
        let item = items[i].split("=");
        let name = decodeURIComponent(item[0]);
        let value = decodeURIComponent(item[1]);
        cookieObj[name] = value;
    }
    req.cookie = cookieObj
}

myHttp = new fishHttp()

myHttp.afterReq(getQueryObj,getCookieObj)

myHttp.get("/",(req,res) => {
    res.write("it is /")
    res.end()
})
myHttp.get("/aa/bb",(req,res) => {
    res.write("it is /aa/bb")
    res.end()
})

myHttp.listen(3003)
複製代碼

是否是有幾分自定義模塊的味道了?

相關文章
相關標籤/搜索