# NodeJs 實戰——原生 NodeJS 輕仿 Express 框架從需求到實現(一)

這篇文章是一個系列的文章的第一篇,主要是本身實現一個express的簡易版框架,加深對nodejs的理解。 github地址歡迎拍磚。javascript

下一篇:NodeJs 實戰——原生 NodeJS 輕仿 Express 框架從需求到實現(二)java

確認需求

咱們從一個經典的 Hello World 開始,這是 Express 官方文檔的第一個實例, 代碼以下node

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.jsgit

node helloworld.js
複製代碼

在瀏覽器上打開http://localhost:3000,網頁將顯示 Hello World。github

代碼實現

由 Hello World 實例分析,咱們能夠看出 express 返回了一個函數,而執行這個函數會返回一個類的實例,實例具備 get 和 listen 兩個方法。web

第一步,建立目錄

首先咱們先構建下圖的目錄結構express

第二步,建立入口文件

其中,入口爲 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.stack = [
    {
      path: '*',
      method: '*',
      handle: function(req, res) {
        res.writeHead(200, {
          'Content-Type': 'text/plain'
        });
        res.end('404');
      }
    }
  ];
}

// 在Application的原型上拓展get方法,以便Application的實例具備該方法。
Application.prototype.get = function(path, handle) {
  // 將請求路由壓入棧內
  this.stack.push({
    path,
    method: 'GET',
    handle
  });
};

// 在Application的原型上拓展listen方法,以便Application的實例具備該方法。
Application.prototype.listen = function() {
  const server = http.createServer((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 = this.stack.length; i < len; i++) {
      if (
        (req.url === this.stack[i].path || this.stack[i].path === '*') &&
        (req.method === this.stack[i].method || this.stack[i].method === '*')
      ) {
        return this.stack[i].handle && this.stack[i].handle(req, res);
      }
    }
    return this.stack[0].handle && this.stack[0].handle(req, res);
  });
  return server.listen.apply(server, arguments);
};

module.exports = Application;
複製代碼

總結

咱們這裏主要實現了express簡單的搭建服務器和get請求方法的功能,知足了Hello World這個簡單實例的要求。

下一篇:NodeJs 實戰——原生 NodeJS 輕仿 Express 框架從需求到實現(二)

相關文章
相關標籤/搜索