簡易實現一個express

文章首發於:github.com/USTB-musion…html

寫在前面

Express是一個簡潔、靈活的node.js Web應用開發框架,它提供一系列強大的特性,幫助你建立各類web和移動應用。豐富的HTTP快捷方法和任意排列組合的Connect中間件,讓你建立健壯、友好的API變得既快捷又簡單。node

本文將從如下幾部分進行總結:git

  1. 路由的實現
  2. 中間件的實現
  3. 內置中間件實現

路由的實現

簡單地說,就是根據方法和路徑執行匹配成功後執行對應的回調函數。如下爲路由的使用例子:github

// express是一個函數
let express = require('./express-route')
// 監聽函數
let app = express()

// RESTFUL API根據方法名的不一樣 作對應的資源的處理
app.get('/name', (req, res) => {
  res.end('name')
})

app.get('/age', (req, res) => {
  res.end('9')
})

app.post('/name', (req, res) => {
  res.end('post name')
})

// all匹配全部的方法, *表示匹配全部的路徑
app.all('*', function(req, res) {
  res.end(req.method)
})

app.listen(3000, () => {
  console.log('server start 3000')
})
複製代碼

經過觀察上面的例子能夠發現,能夠定義一個express-route.js文件,並暴露出express函數,get,post,all,listen等方法,將其引入便可運行上面的例子。如下爲簡易實現express路由的代碼:web

let http = require('http');
let url = require('url');
function createApplication() {
  // 監聽函數
  let app = (req, res) => {
    // 取出每個layer
    // 1. 獲取請求的方法
    let getMethods = req.method.toLowerCase();
    let { pathname } = url.parse(req.url, true);

    for (let i = 0; i < app.routes.length; i++) {
      let {method, path, handler} = app.routes[i];
      if ((method === getMethods || method === 'all') && (path === pathname || path === '*')) {
        // 匹配成功後執行對應的callback
        handler(req, res);
      }
    }
    res.end(`Cannot ${getMethods} ${pathname}`)
  }
  // 存放路由
  app.routes = [];
  // 實現all方法
  app.all = function(path, handler) {
    let layer = {
      method: 'all', // 表示所有匹配
      path,
      handler
    }
    app.routes.push(layer);
  }
  // http.METHODS能夠獲取全部的http方法
  http.METHODS.forEach(method => {
    // 將方法名轉換成小寫的
    method = method.toLocaleLowerCase();
    // 實現get,post等方法
    app[method] = function(path, handler) {
      let layer = {
        method,
        path,
        handler
      }
      app.routes.push(layer);
    }
  })
  // 實現get方法
  // app.get = function(path, handler) {
  //   let layer = {
  //     method: 'get',
  //     path,
  //     handler
  //   }
  //   app.routes.push(layer);
  // }
  // 實現listen方法
  app.listen = function() {
    let server = http.createServer(app)
    server.listen(...arguments)
  }
  return app
}

module.exports = createApplication;
複製代碼

經過向外暴露一個createApplication函數,並返回app對象。經過http.METHODS能夠獲取全部的http方法,在app對象裏定義listen,all還有get,post等請求方法。當客戶端請求時即遍歷routes,當匹配到理由時,即執行相應的handler函數。express

中間件的實現

如上圖所示,中間件就是處理HTTP請求的函數,用來完成各類特定的任務,好比檢查用戶是否登陸,檢查用戶是否有權限訪問,執行一個請求須要多長時間等。它的特色是:數組

  • 一箇中間件處理完請求和響應後能夠把相應的數據再傳遞給下一個中間件
  • 回調函數的next參數表示接受其餘中間件的的調用
  • 能夠根據路徑區分返回執行不一樣的的中間件 如下爲express中間件的簡單使用例子:
// middleware use
// 中間件 在執行路由以前 要幹一些處理工做 就能夠採用中間件
let express = require('./express-middleware.js');

let app = express();

// use方法第一個參數缺省默認就是/
// 中間件能夠擴展一些方法
app.use('/name', function(req, res, next) {
  res.setHeader('Content-Type','text/html;charset=utf-8');
  console.log('middleware1');
  next('名字不合法')
})

app.use('/', function(req, res, next) {
  console.log('middleware2');
  next();
})

app.get('/name', (req, res, next) => {
  // res.setHeader('Content-Type','text/html;charset=utf-8');
  res.end('姓名');
  next('名字不合法')
})

app.get('/age', (req, res) => {
  console.log(req.path);
  console.log(req.hostname);
  console.log(req.query);
  res.end('年齡');
})

// 錯誤中間件(4個參數)放在路由的下面
app.use(function(err, req, res, next) {
  console.log(err)
  next()
})

app.listen(3001, () => {
  console.log('server start 3001')
})
複製代碼

經過觀察上面的例子能夠發現,app.use方法調用中間件,其中next爲很是重要的一個參數。要運行上面的例子,能夠定義一個express-middware.js,來實現app.use方法,若是運行next,會調用下一個下一個中間件。其大體原理爲:定義一個next函數並定義一個index索引值,每調用一次next,索引值加1。若是當前的layer與routes中的項相等,則匹配成功。實現express中間件的簡易代碼以下:bash

let http = require('http');
let url = require('url');
function createApplication() {
  // 監聽函數
  let app = (req, res) => {
    // 取出每個layer
    // 獲取請求的方法
    let getMethods = req.method.toLowerCase();
    let { pathname } = url.parse(req.url, true);

    // 經過next方法進行迭代
    let index = 0;
    function next(err) {
      // 若是數組所有迭代完成尚未找到,說明路徑不存在
      if (index === app.routes.length) {
        return res.end(`Cannot ${getMethods} ${pathname}`)
      }
      // 每次調用next方法調用下一個layer
      let { method, path, handler } = app.routes[index++];
      if (err) {
        // 若是有錯誤,應該去找錯誤中間件,錯誤中間件有一個特色,handler有4個參數
        if (handler.length === 4) {
          handler(err, req, res, next)
        } else {
          // 若是沒有匹配到,要將err繼續傳遞下去
          next(err); // 繼續找下一個layer進行判斷
        }
      } else {
        // 處理中間件
        if (method === 'middleware') {
          if (path === '/' || path === pathname || pathname.startsWith(path + '/')) {
            handler(req, res, next);
          } else {
            // 若是這個中間件沒有匹配到,那麼繼續走下一個中間件匹配
            next(); 
          }
        } else { // 處理路由
          if ((method === getMethods || method === 'all') && (path === pathname || path === '*')) {
            // 匹配成功後執行對應的callback
            handler(req, res);
          } else {
            next();
          }
        }
      }
    }
    // 中間件中的next方法
    next();

    res.end(`Cannot ${getMethods} ${pathname}`)
  }
  // 存放路由
  app.routes = [];
  // 實現use方法
  app.use = function(path, handler) {
    // use方法第一個參數能夠缺省,默認是/
    if (typeof handler !== 'function') {
      handler = path;
      path = '/';
    }
    let layer = {
      // 約定method是'middleware'就表示是一箇中間件
      method: 'middleware', 
      path,
      handler
    }
    // 將中間件放在容器內
    app.routes.push(layer);
  }
  // express內置中間件
  app.use(function(req, res, next) {
    let { pathname, query } = url.parse(req.url, true);
    let hostname = req.headers['host'].split(':')[0];
    req.path = pathname;
    req.query = query;
    req.hostname = hostname;
    next();
  })
  // 實現all方法
  app.all = function(path, handler) {
    let layer = {
      method: 'all', // 表示所有匹配
      path,
      handler
    }
    app.routes.push(layer);
  }
  // http.METHODS能夠獲取全部的http方法
  http.METHODS.forEach(method => {
    // 將方法名轉換成小寫的
    method = method.toLocaleLowerCase();
    // 實現get,post等方法
    app[method] = function(path, handler) {
      let layer = {
        method,
        path,
        handler
      }
      app.routes.push(layer);
    }
  })
  // 實現listen方法
  app.listen = function() {
    let server = http.createServer(app)
    server.listen(...arguments)
  }
  return app
}

module.exports = createApplication;
複製代碼

內置中間件的實現

express經過中間件的形式擴展了不少的內置API,舉一個簡單的例子:app

app.use(function(req, res, next) {
    let { pathname, query } = url.parse(req.url, true);
    let hostname = req.headers['host'].split(':')[0];
    req.path = pathname;
    req.query = query;
    req.hostname = hostname;
    next();
  })
複製代碼

便可在req封裝hostname, path, query, hostname。框架

相關文章
相關標籤/搜索