Node.js 系列:原生 Node.js 應用

原生 Node.js 應用

Node.js 是一個基於 Chrome V8 引擎的 JavaScript 運行環境
Node.js 使用了一個事件驅動、非阻塞式 I/O 的模型,使其輕量又高效
Node.js 的包管理器 npm,是全球最大的開源庫生態系統

? 本文主要介紹構建一個 Node.js 應用的基本步驟和模塊,並假定你已經對 Node.js Api 有必定的瞭解javascript

? 本文引用部分代碼做爲例子,若是但願參看所有源碼,歡迎去 github 查閱(若是以爲有必定幫助,歡迎star)html

模塊架構設計

整個 Node.js 應用的架構設計java

original

node.js 應用構成

  • 引入模塊:經過 require 指令來引入 Node.js 模塊
  • 建立服務器:服務器用來監聽客戶端請求
  • 接收請求和響應請求:接收客戶端的HTTP請求,返回響應數據
// 經過 require 引入 http 模塊,並將實例化的 HTTP 賦值給 http 變量
const http = require('http');
// 引入 url 模塊,用來解析數據
const url = require('url');

function ylone(router, handleObj) {
    const hostname = '127.0.0.1';
    const port = 7777;
    // http.createServer(function(){}) 方法建立服務器,並返回一個對象
    const server = http.createServer((req, res) => {
        const path = url.parse(req.url);
        const pathName = path.pathname;
        // 處理node.js每次自動請求favicon.ico
        if (pathName !== '/favicon.ico') {
          const content = router(handleObj, pathName, res, req);
        }
    });

    // server.listen() 方法綁定主機和端口
    server.listen(port, hostname, () => {
        console.log(`服務運行在${hostname}:${port}`);
    });
}

exports.ylone = ylone;

基於事件驅動的回調

  • http.createServer((req, res) => {...}) 是一個典型的回調,事實上,這就是 Node.js 原生的工做方式
  • 這裏直接將一個匿名函數做爲變量進行傳遞,繞開了「先定義,再傳遞」的圈子
  • 像寫 PHP 應用時:任什麼時候候當有請求進入時,服務器(如Apache)就會爲這個請求建立一個新的進程,而且從頭到尾執行相應的 PHP 腳本
  • 在 Node.js 中,不管什麼時候當一個新的請求到達指定端口時,咱們在服務器建立時傳遞的函數就會被調用

Node.js 模塊

  • 模塊意味着將 Node.js 應用(如 http.js)抽象成一個模塊,經過入口文件 index.js 去調用相應的模塊來引導和啓動應用
  • 將代碼模塊化意味着咱們將提供其功能的部分(如 一個函數)導出到請求這個模塊的腳本內
const server = require('./http');
const router = require('./route');
const handle = require('./requestHandle');

var handleObj = {};
// 入口 Case
handleObj['/'] = handle.hello;
// 非阻塞 Case
handleObj['/vlone'] = handle.vlone;
// post Case
handleObj['/supreme'] = handle.supreme;
// get Case
handleObj['/adidas'] = handle.adidas;

server.ylone(router.router, handleObj);

路由

  • 爲了處理不一樣的 http 請求,咱們須要經過建立一個路由模塊來進行「路由選擇」
  • 爲路由提供請求的 url 和其餘須要的 get 和 post 參數,隨後路由根據這些數據來執行相應的代碼
  • 咱們所須要的數據都在 http.createServer((req, res) => {...}) 的 req 參數中,爲了解析 req,須要額外引入 urlquerystring Node.js 模塊
  • 在 index.js 內引入路由對象,將路由方法傳遞給 http 應用,在 http.createServer((req, res) => {...}) 內解析 req 參數,而後調用 router 方法
  • 理解如下函數式編程:將 router 對象傳遞給 index,在 index 內將 router 方法傳遞給 http,由於 http 並不關心 router 方法從哪來,只須要執行方法,而後完成業務,可是首先須要保證有這個對象
  • 函數式編程最基本,最核心的即思想轉換,由名詞到動詞,由對象到方法,行爲驅動執行
function router(handleObj, pathName, res, req) {
  if (typeof handleObj[pathName] === 'function') {
    return handleObj[pathName](res, req);
  } else {
    res.writeHead(200, {
          'Content-type': 'text/plain'
    });
    const content = '404 Not Found';
    res.write(content);
    res.end();
  }
}

exports.router = router;

將路由分發到請求處理函數

  • 須要建立一個新的 resquestHandlers 模塊,封裝各個處理函數來對應不一樣的請求
  • 在 JavaScript 中經過對象鍵值對來封裝 路徑->方法 的映射關係
  • 在 C++ 或者 C# 中,對象指的是類或者結構體的實例,對象根據其實例化的模板會擁有不一樣的屬性和方法
  • 在 JavaScript 中,對象是一個鍵值對集合
  • 在入口文件(index.js)內引入 requestHandle,同時聲明一個操做對象(handleObj),用來存儲 路徑->方法 的映射關係,最後將路由方法和操做對象傳遞給服務器應用(http.js)
  • 在服務器應用內,得到瀏覽器請求的路徑,調用路由方法(router),將操做對象(handleObj)和請求路徑做爲參數傳遞
  • 在路由內(route.js)獲取路徑對應的函數,自執行函數

由於文章篇幅緣由,這裏只展現關鍵代碼,源碼參看 githubnode

const { exec } = require('child_process');
const querystring = require('querystring');
const url = require('url');

function createHttp(type, res, val) {
    const content = val;
    const conType = {
        plain: 'text/plain;charset=utf-8',
        html: 'text/html',
    };
    // 爲隱式的響應頭設置值
    res.writeHead(200, {
        'Content-type': conType[type]
    });
    // 發送響應主體
    res.write(content);
    // http 完成響應
    res.end();
}

... something else

function vlone(res) {
    exec('node --version', (error, stdout, stderr) => {
        if (error) {
            console.log(error, stdout, stderr);
            return;
        }
        const content = stdout;
        const type = 'plain';
        createHttp(type, res, content);
    });
}

... something else

阻塞與非阻塞

A() 方法讀取文件,所以須要必定的響應時間,B() 方法表明其餘須要執行的代碼git

阻塞:在A() 執行的過程當中,B() 處於等待狀態,當A() 訪問文件數據準備就緒後,B() 纔開始執行github

阻塞IO模型

由上圖能夠看出,應用程序從進行系統調用到複製數據報到應用進程緩衝區的整段過程是阻塞的,直到數據報被複制到用戶空間完成後,用戶進程才解除阻塞狀態,繼續執行下一個應用程序npm

  • 優勢:可以及時返回數據,無延遲,方便調試
  • 缺點:須要等待

非阻塞:在A() 執行的過程當中,B() 同時執行,且當A() 訪問文件數據準備就緒後,A() 會被執行完成編程

非阻塞IO模型

由上圖能夠看出,應用程序在調用過程當中,若是數據報尚未準備就緒,會先返回一個錯誤信息(EWOULDBLOCK),此時當前進程能夠執行其餘方法,而不會阻塞。而 A() 會輪詢內核,返回緩衝區數據是否準備就緒api

  • 優勢:不須要等待,當前線程能夠處理多個任務
  • 缺點:增大了任務完成的響應延遲,由於任務可能在兩次輪詢間隔內完成,從而致使總體數據的吞吐量下降

以非阻塞方式進行請求響應

  • 當前的應用交互方式:(請求處理程序 -> 請求路由 -> 服務器)將請求處理程序返回的內容(請求處理程序最終要顯示給用戶的內容)傳遞給HTTP服務器
  • 當前這種交互方式的問題在於,若是請求處理程序中有 Node.js 封裝的非阻塞方法A(),那麼A() 在阻塞過程當中,服務器就已經將數據返回了,並不會等到A() 執行完畢
  • 爲了解決上述問題,以非阻塞方式進行請求響應,相對於以前將數據傳遞給服務器的方式,如今咱們須要將服務器(response對象)傳遞給生成數據的應用內,待數據準備完畢,再返回響應數據
  • 這樣能夠同時請求兩個路徑(實際上就是觸發兩個函數方法),B() 並不會由於A()執行時間長而處於等待狀態

處理 post 請求

  • 建立一個表單元素,設置表單提交方法爲 post, 每當用戶提交表單時,則觸發 supreme() 方法
  • 處理 post 請求通常採用異步非阻塞方式,由於 post 請求通常會比較重,你沒法控制用戶輸入的數據量,若是用阻塞的方式處理則必然會致使用戶操做阻塞
  • 爲了實現非阻塞,Node.js 會將 post 數據拆分紅數據塊,而後經過出發特定事件,將這些數據塊傳遞給回調函數
  • 經常使用的 post 兩個事件:data事件(新的數據塊到達時觸發),end事件(全部數據都已經接收完畢時觸發)
  • 經過在 request 對象上註冊監聽器(listener)來告訴應用當 post 事件觸發時,應該觸發哪些回調函數

處理 get 請求

  • 經過 Node.js 封裝的 url對象來解析 url 參數,獲取關鍵數據
  • url.parse() 的第二參數 parseQueryString 若是爲 true,則 query 屬性老是會經過 querystring 模塊的 parse() 方法生成一個對象

some pieces

  • 當寫好 Node.js 腳本(如 ylone.js)後,經過 node ylone.js 命令執行腳本
  • 在瀏覽器訪問指定地址(如 http://localhost:7777/)意味着向服務器發出請求,從而觸發服務器建立時的回調函數
  • 當訪問網頁(如 http://localhost:7777/)時,控制檯可能會輸出兩次 req 的數據,那是由於大部分瀏覽器會在訪問網頁時嘗試讀取 favicon.ico 文件
  • 針對瀏覽器每次發送請求,都會默認請求一次 /favicon.ico 的問題,能夠在 http 中對其進行過濾,不執行操做
  • 若是但願在 Node.js 內的傳遞一個 html 片斷,並渲染在瀏覽器上,須要將 res.writeHead(200, {'Content-type': 'text/plain'}) 的 Content-type 設置爲 text/html
  • Node.js 返回數據(response)在瀏覽器展現亂碼,經過在 res.writeHead(200, {'Content-type': 'text/plain;charset=utf-8'}) 加上 charset=utf-8 配置解決

--Respect Node.js--瀏覽器

相關文章
相關標籤/搜索