1. 程式人生 > >Nodejs進階:Express常用中介軟體body-parser實現解析

Nodejs進階:Express常用中介軟體body-parser實現解析

原文連結

body-parser是非常常用的一個express中介軟體,作用是對post請求的請求體進行解析。使用非常簡單,以下兩行程式碼已經覆蓋了大部分的使用場景。

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

本文從簡單的例子出發,探究body-parser的內部實現。至於body-parser如何使用,感興趣的同學可以參考官方文件

入門基礎

在正式講解前,我們先來看一個POST請求的報文,如下所示。

POST /test HTTP/1.1
Host: 127.0.0.1:3000
Content-Type
: text/plain; charset=utf8 Content-Encoding: gzip chyingp

其中需要我們注意的有Content-TypeContent-Encoding以及報文主體:

  • Content-Type:請求報文主體的型別、編碼。常見的型別有text/plainapplication/jsonapplication/x-www-form-urlencoded。常見的編碼有utf8gbk等。
  • Content-Encoding:宣告報文主體的壓縮格式,常見的取值有gzipdeflateidentity
  • 報文主體:這裡是個普通的文字字串chyingp

body-parser主要做了什麼

body-parser實現的要點如下:

  1. 處理不同型別的請求體:比如textjsonurlencoded等,對應的報文主體的格式不同。
  2. 處理不同的編碼:比如utf8gbk等。
  3. 處理不同的壓縮型別:比如gzipdeflare等。
  4. 其他邊界、異常的處理。

一、處理不同型別請求體

為了方便讀者測試,以下例子均包含服務端、客戶端程式碼,完整程式碼可在筆者github上找到。

解析text/plain

客戶端請求的程式碼如下,採用預設編碼,不對請求體進行壓縮。請求體型別為text/plain

var http = require('http');

var options = {
    hostname: '127.0.0.1'
, port: '3000', path: '/test', method: 'POST', headers: { 'Content-Type': 'text/plain', 'Content-Encoding': 'identity' } }; var client = http.request(options, (res) => { res.pipe(process.stdout); }); client.end('chyingp');

服務端程式碼如下。text/plain型別處理比較簡單,就是buffer的拼接。

var http = require('http');

var parsePostBody = function (req, done) {
    var arr = [];
    var chunks;

    req.on('data', buff => {
        arr.push(buff);
    });

    req.on('end', () => {
        chunks = Buffer.concat(arr);
        done(chunks);
    });
};

var server = http.createServer(function (req, res) {
    parsePostBody(req, (chunks) => {
        var body = chunks.toString();
        res.end(`Your nick is ${body}`)
    });
});

server.listen(3000);

解析application/json

客戶端程式碼如下,把Content-Type換成application/json

var http = require('http');
var querystring = require('querystring');

var options = {
    hostname: '127.0.0.1',
    port: '3000',
    path: '/test',
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'Content-Encoding': 'identity'
    }
};

var jsonBody = {
    nick: 'chyingp'
};

var client = http.request(options, (res) => {
    res.pipe(process.stdout);
});

client.end( JSON.stringify(jsonBody) );

服務端程式碼如下,相比text/plain,只是多了個JSON.parse()的過程。

var http = require('http');

var parsePostBody = function (req, done) {
    var length = req.headers['content-length'] - 0;
    var arr = [];
    var chunks;

    req.on('data', buff => {
        arr.push(buff);
    });

    req.on('end', () => {
        chunks = Buffer.concat(arr);
        done(chunks);
    });
};

var server = http.createServer(function (req, res) {
    parsePostBody(req, (chunks) => {
        var json = JSON.parse( chunks.toString() );    // 關鍵程式碼    
        res.end(`Your nick is ${json.nick}`)
    });
});

server.listen(3000);

解析application/x-www-form-urlencoded

客戶端程式碼如下,這裡通過querystring對請求體進行格式化,得到類似nick=chyingp的字串。

var http = require('http');
var querystring = require('querystring');

var options = {
    hostname: '127.0.0.1',
    port: '3000',
    path: '/test',
    method: 'POST',
    headers: {
        'Content-Type': 'form/x-www-form-urlencoded',
        'Content-Encoding': 'identity'
    }
};

var postBody = { nick: 'chyingp' };

var client = http.request(options, (res) => {
    res.pipe(process.stdout);
});

client.end( querystring.stringify(postBody) );

服務端程式碼如下,同樣跟text/plain的解析差不多,就多了個querystring.parse()的呼叫。

var http = require('http');
var querystring = require('querystring');

var parsePostBody = function (req, done) {
    var length = req.headers['content-length'] - 0;
    var arr = [];
    var chunks;

    req.on('data', buff => {
        arr.push(buff);
    });

    req.on('end', () => {
        chunks = Buffer.concat(arr);
        done(chunks);
    });
};

var server = http.createServer(function (req, res) {
    parsePostBody(req, (chunks) => {
        var body = querystring.parse( chunks.toString() );  // 關鍵程式碼
        res.end(`Your nick is ${body.nick}`)
    });
});

server.listen(3000);

二、處理不同編碼

很多時候,來自客戶端的請求,採用的不一定是預設的utf8編碼,這個時候,就需要對請求體進行解碼處理。

客戶端請求如下,有兩個要點。

  1. 編碼宣告:在Content-Type最後加上;charset=gbk
  2. 請求體編碼:這裡藉助了iconv-lite,對請求體進行編碼iconv.encode('程式猿小卡', encoding)
var http = require('http');
var iconv = require('iconv-lite');

var encoding = 'gbk';  // 請求編碼

var options = {
    hostname: '127.0.0.1',
    port: '3000',
    path: '/test',
    method: 'POST',
    headers: {
        'Content-Type': 'text/plain; charset=' + encoding,
        'Content-Encoding': 'identity',        
    }
};

// 備註:nodejs本身不支援gbk編碼,所以請求傳送前,需要先進行編碼
var buff = iconv.encode('程式猿小卡', encoding);

var client = http.request(options, (res) => {
    res.pipe(process.stdout);
});

client.end(buff, encoding);

服務端程式碼如下,這裡多了兩個步驟:編碼判斷、解碼操作。首先通過Content-Type獲取編碼型別gbk,然後通過iconv-lite進行反向解碼操作。

var http = require('http');
var contentType = require('content-type');
var iconv = require('iconv-lite');

var parsePostBody = function (req, done) {
    var obj = contentType.parse(req.headers['content-type']);
    var charset = obj.parameters.charset;  // 編碼判斷:這裡獲取到的值是 'gbk'

    var arr = [];
    var chunks;

    req.on('data', buff => {
        arr.push(buff);
    });

    req.on('end', () => {
        chunks = Buffer.concat(arr);
        var body = iconv.decode(chunks, charset);  // 解碼操作
        done(body);
    });
};

var server = http.createServer(function (req, res) {
    parsePostBody(req, (body) => {
        res.end(`Your nick is ${body}`)
    });
});

server.listen(3000);

三、處理不同壓縮型別

這裡舉個gzip壓縮的例子。客戶端程式碼如下,要點如下:

  1. 壓縮型別宣告:Content-Encoding賦值為gzip
  2. 請求體壓縮:通過zlib模組對請求體進行gzip壓縮。
var http = require('http');
var zlib = require('zlib');

var options = {
    hostname: '127.0.0.1',
    port: '3000',
    path: '/test',
    method: 'POST',
    headers: {
        'Content-Type': 'text/plain',
        'Content-Encoding': 'gzip'
    }
};

var client = http.request(options, (res) => {
    res.pipe(process.stdout);
});

// 注意:將 Content-Encoding 設定為 gzip 的同時,傳送給服務端的資料也應該先進行gzip
var buff = zlib.gzipSync('chyingp');

client.end(buff);

服務端程式碼如下,這裡通過zlib模組,對請求體進行了解壓縮操作(guzip)。

var http = require('http');
var zlib = require('zlib');

var parsePostBody = function (req, done) {
    var length = req.headers['content-length'] - 0;
    var contentEncoding = req.headers['content-encoding'];
    var stream = req;

    // 關鍵程式碼如下
    if(contentEncoding === 'gzip') {
        stream = zlib.createGunzip();
        req.pipe(stream);
    }

    var arr = [];
    var chunks;

    stream.on('data', buff => {
        arr.push(buff);
    });

    stream.on('end', () => {
        chunks = Buffer.concat(arr);        
        done(chunks);
    });

    stream.on('error', error => console.error(error.message));
};

var server = http.createServer(function (req, res) {
    parsePostBody(req, (chunks) => {
        var body = chunks.toString();
        res.end(`Your nick is ${body}`)
    });
});

server.listen(3000);

寫在後面

body-parser的核心實現並不複雜,翻看原始碼後你會發現,更多的程式碼是在處理異常跟邊界。

另外,對於POST請求,還有一個非常常見的Content-Typemultipart/form-data,這個的處理相對複雜些,body-parser不打算對其進行支援。篇幅有限,後續章節再繼續展開。

歡迎交流,如有錯漏請指出。


相關推薦

NodejsExpress常用中介軟體body-parser實現解析

原文連結 body-parser是非常常用的一個express中介軟體,作用是對post請求的請求體進行解析。使用非常簡單,以下兩行程式碼已經覆蓋了大部分的使用場景。 app.use(bodyParser.json()); app.use(bodyParser.urle

[轉] Nodejs Express 常用中間件 body-parser 實現解析

tree define pan iconv 不同 erro unzip body message 寫在前面 body-parser是非常常用的一個express中間件,作用是對post請求的請求體進行解析。使用非常簡單,以下兩行代碼已經覆蓋了大部分的使用場景。 app.

Nodejs基於express+multer的文件上傳

ora all server and end 文件類型 類型 array body 安裝組件 npm install express multer --save 服務端代碼server.js var Express = re

nodejs密碼加鹽隨機鹽值

nod sharp class oms word blog 輸出 arp har demo var crypto = require(‘crypto‘); function getRandomSalt(){ return Math.random().toStri

NodeJS學習筆記 (11)Nodejs 調試日誌打印debug模塊

-c clas a* deb urn uid 0.11 log 打印 前言 在node程序開發中時,經常需要打印調試日誌。用的比較多的是debug模塊,比如express框架中就用到了。下文簡單舉幾個例子進行說明。文中相關代碼示例,可在這裏找到。 備註:node在0.11

Node express 預設日誌元件 morgan 使用筆記

1.安裝 npm install express morgan 2.使用案例(預設) var express = require('express'); var app = express(); var morgan = require('morgan'); app.use(morga

express常用中介軟體

 整理一下工作中經常使用到的Express中介軟體 config-lite: 讀取配置檔案 express-session: session 中介軟體 connect-mongo: 將 session 持久化儲存於 mongodb,結合 express-sess

程式設計師怎麼成為一個軟體架構師?

作者:程式設計小丫 來源:CSDN部落格 序:的確沒想到隨手寫的東西有那麼多的回覆,不管怎樣還是挺高興的。在這裡謝謝大家的關注了。其實做了這麼多年的技術腦子裡總會跳出很多的想法,但很少有時間靜下來仔細地思考思考,寫寫部落格也算是一種自我歸納和總結吧。  “軟體架構師”這個名詞也不知是什麼時候

SpringBoot2基礎,,資料庫,中介軟體等系列文章目錄分類

一、文章分類 1、入門基礎 SpringBoot2:環境搭建和RestFul風格介面 2、日誌管理 SpringBoot2:配置L

我的Android之旅------>Android自定義View來實現解析lrc歌詞並同步滾動、上下拖動、縮放歌詞的功能

前言 最近有個專案有關於播放音樂時候,關於歌詞有以下幾個功能: 1、實現歌詞同步滾動的功能,即歌曲播放到哪句歌詞,就高亮地顯示出正在播放的這個歌詞; 2、實現上下拖動歌詞時候,可以拖動播放器的進度。即可以不停地上下拖動歌詞,

nodejs常用框架express中介軟體 及app.use 和 app.get 方法

用node開發專案,express是常用的框架,下面介紹下核心用法中介軟體和 app的use、get方法: 中介軟體的實現很簡單: // 一個簡單的中介軟體 function middleware(req, res, next){ // req 引數可以接受一些請求的引數(req.q

NodeJS學習筆記 (8)express+session實現簡易身份認證(ok)

fin dir 認證 ole opts ats ssi oda 添加 章節概覽 morgan是express默認的日誌中間件,也可以脫離express,作為node.js的日誌組件單獨使用。本文由淺入深,內容主要包括: morgan使用入門例子 如何將日誌保存到本地文件

原 Android步驟三Android常用框架Picasso圖片框架

轉載: 作者:依然範特稀西 連結:https://www.jianshu.com/p/c68a3b9ca07a Android 中有幾個比較有名的圖片載入框架,Universal ImageLoader、Picasso、Glide和Fresco。它們各有優點,以前一直用的是ImageLoader

原 Android步驟三Android常用框架OkHttp網路操作框架

Okio & OkHttp 課程目標  掌握I/O操作的方法  掌握傳輸資料的方法 學習內容 Okio簡介  Okio的核心類  OkHttp簡介  OkHttp核心類  程式碼實踐 一、Okio簡介 什

【學習筆記】StringStringBuffer類(線程安全)和StringBuilder類

n) static this util double 字符串 對象 ice 單線程 一、除了使用String類存儲字符串之外,還可以使用StringBuffer類存儲字符串。而且它是比String類更高效的存儲字符串的一種引用數據類型。 優點:   對字符串進行連接操作時,

python開發函數命名空間,作用域,函數的本質,閉包,內置方法(globales)

問題 總結 加載 自己的 ger 作用域 範圍 沒有 概念 一,命名空間 #局部命名空間#全局命名空間#內置命名空間 #三者的順序#加載順序 硬盤上——內存裏#內置-->全局(從上到下順序加載進來的)-->局部(調用的時候加載) 1 #!/usr/bin/

python開發函數裝飾器

for 中國 eas login please 函數 功能 log 原則 一,裝飾器本質 閉包函數 功能:就是在不改變原函數調用方式的情況下,在這個函數前後加上擴展功能 二,設計模式 開放封閉原則 *對擴展是開放的 *對修改是封閉的 三,代碼解釋 1 #!/

[您有新的未分配科技點]博弈論似乎不那麽恐懼了…… (SJ定理,簡單的基礎模型)

裏的 如果 cnblogs 經典 ant 控制 nim osi 取石子 這次,我們來繼續學習博弈論的知識。今天我們會學習更多的基礎模型,以及SJ定理的應用。 首先,我們來看博弈論在DAG上的應用。首先來看一個小例子:在一個有向無環圖中,有一個棋子從某一個點開始一直向它的出點

python開發函數遞歸函數

bre for 自己 lis 一次 技術 結束 函數 ont 一,什麽叫遞歸 #遞歸#在一個函數裏調用自己#python遞歸最大層數限制 997#最大層數限制是python默認的,可以做修改#但是我們不建議你修改 例子和尚講故事 1 #!/usr/bin/env pyt

python開發函數可叠代的&叠代器&生成器

== ict turn lena log 中新 odin 使用 def 一,可叠代的&可叠代對象 1.一個一個的取值就是可叠代的 iterable#str list tuple set dict#可叠代的 ——對應的標誌 __iter__ 2.判斷一個變量