自定义 Express 中间件以实现 express.urlencoded() 的类似功能

参考

项目描述
哔哩哔哩黑马程序员
搜索引擎Bing

描述

项目描述
Edge109.0.1518.61 (正式版本) (64 位)
NodeJSv18.13.0
nodemon2.0.20
Express4.18.2

express.urlencoded()

中间件 express.urlencoded() 可以将客户端以 POST 方式提交的 application/x-www-form-urlencoded数据转换为 JavaScript 对象。

比如,express.urlencoded() 可以将如下数据转换为:

name=RedHeart&age=18&gender=男

转换为:

{ name: ‘RedHeart’, age: 18, gender: ‘男’ }

注:

这个转换过程中进行了如下两个步骤:

  1. 对中文字符进行 URL 解码,由客户端发送的非 ASCII 字符将被 URL 编码,所以服务器端若想获得客户端发出的原始数据需要对客户端提交的数据进行 URL 解码。
  2. 对客户端发送的数据进行解析,拆分出其中的参数及参数值并将这些数据转换为 JavaScript 对象。

版本:

Express 模块在 4.16.0 后才添加了内置中间件 express.urlencoded() ,在该版本以前,该功能主要由 Express 的第三方中间件 body-parser 承担。如果你使用 npm(NodeJS Package Manger) ,那么你可以使用如下命令对该中间件进行下载安装。

npm install body-parser

举个栗子:

如果你希望使用 express.urlencoded() 中间件来处理客户端以 POST 方式发送的 application/x-www-form-urlencoded 格式的数据,可以使用如下代码搭建一个简单的服务器端:

const express = require('express');


const app = express();
// 监听本机的 9090 端口
app.listen(9090);


// 注册 express.urlencoded() 中间件以处理客户端
// 以 POST 方式发送的 
// application/x-www-form-urlencoded 格式的数据

// extended: false 使该中间件不要解析被嵌套的数据
app.use(express.urlencoded({ extended: false} ));

// 创建路由
app.post('/', (req, res, next) => {
    // 你可以通过 req.body 获取客户端发送的
    // application/x-www-form-urlencoded 格式的数据
    // 如果你没有使用相关的中间件处理 POST 请求
    // 数据,req.body 将为 undefined。
    res.send(req.body);
})

自定义中间件

当 Express 接收到客户端发送的数据时,将触发 Express 中的 req 对象的 data 事件,而当客户端的数据发送完成时将触发 Express 中的 req 对象的 end 事件。

data

const express = require('express');

const app = express();
// 使服务器监听本机的 9090 端口
app.listen(9090);

// 在接收到客户端发送的数据后将触发 data
// 事件,获取的数据可以通过向该事件的回调函数传递
// 参数来进行获取.
function mw(req, res, next){
    req.on('data', (chunk) => {
    	req.body = chunk.toString();
        console.log(req.body);
    })
    // 将执行权交给下一个中间件或函数
    next();
}

// 将自定义中间件注册为全局中间件
app.use(mw);

// 创建路由
app.post('/', (req, res, next) => {
	// 向服务器端发送响应数据
    res.send('Hello World');
})

注:

  1. 通过 data 接收到的数据为 NodeJS 中的内置对象 Buffer ,我们可以使用该对象的 toSring() 方法将 Buffer 转换为字符串。
  2. 如果客户端发送的数据量较大,则数据可能被切分为多块进行发送。在这种情况下 data 事件将触发多次,而我们通过 data 事件获取到的客户端数据也不会完整。
    所以,我们应该将通过 req 对象的 data 事件获取到的提交数据进行拼接。防止接收到的数据不完整。

执行效果:

在执行上述代码后,如果你向 127.0.0.1:9090 发送 POST 数据:

name=RedHeart&age=18&gender=男

终端将输出如下内容:

name=RedHeart&age=18&gender=%E7%94%B7

提示:

你可以通过 BurpSuitePostmanHacbar v2 等工具来向服务器发送 POST 请求。
我是使用 Python 来向服务器发送 POST 请求的,如果你也使用 Python,那你可以参考如下代码对服务器发送 POST 请求。

# 导入 Python 第三方模块 requests 以发送请求
import requests

# 定义需要传递的数据
data = {'name': 'RedHeart', 'age': 18, 'gender': '男'}

# 发送 POST 请求
response = requests.request('post', 'http://127.0.0.1:9090', data=data)

# 对服务器端的响应信息进行打印
print(response.text)

end

const express = require('express');

const app = express();
// 使服务器监听本机的 9090 端口
app.listen(9090);

// 在接收到客户端发送的数据后将触发 data
// 事件,获取的数据可以通过向该事件的回调函数传递
// 参数来进行获取.
function mw(req, res, next){  
    // 定义一个空字符串来对接收到的多段(可能)
    // 数据进行拼接.
    req.body = '';
    req.on('data', (chunk) => {
    	req.body += chunk;
        console.log('[data] ' + req.body);
    }) 

    req.on('end', () => {

    })
    // 将执行权交给下一个中间件或函数
    next();
}

// 将自定义中间件注册为全局中间件
app.use(mw);

// 创建路由
app.post('/', (req, res, next) => {
	// 向服务器端发送响应数据
    res.send(req.body);
    console.log('[POST] ' + req.body);
})

注:

字符串与 Buffer 对象进行拼接时,Buffer 对象将自动转换为字符串。所以在本示例中,我们并不需要使用 toString() 方法来将 Buffer 对象转换为字符串。

执行结果:

在执行上述代码后,如果你向 127.0.0.1:9090 发送 POST 数据:

name=RedHeart&age=18&gender=男

终端将输出如下内容:

[POST]
[data] name=RedHeart&age=18&gender=%E7%94%B7

分析:

分析终端的输出结果在,可以观察到打印的顺序并不符合我们的预期,打印的顺序发生了颠倒。这是因为:

next() 函数过早执行,导致全局中间件还没有对事件进行捕获,路由就已经开始运行了。我们可以将 next() 函数放置在 end 事件的处理函数中,在数据完全接收完毕后,才使用 next() 将执行权交给路由。

正确示范:

const express = require('express');

const app = express();
// 使服务器监听本机的 9090 端口
app.listen(9090);

// 在接收到客户端发送的数据后将触发 data
// 事件,获取的数据可以通过向该事件的回调函数传递
// 参数来进行获取.
function mw(req, res, next){  
    // 定义一个空字符串来对接收到的多段(可能)
    // 数据进行拼接.
    req.body = '';
    req.on('data', (chunk) => {
    	req.body += chunk;
        console.log('[data] ' + req.body);
    }) 

    req.on('end', () => {
        // 将执行权交给下一个中间件或函数
        next();
    })
}

// 将自定义中间件注册为全局中间件
app.use(mw);

// 创建路由
app.post('/', (req, res, next) => {
	// 向服务器端发送响应数据
    res.send(req.body);
    console.log('[POST] ' + req.body);
})

在执行上述代码后,如果你向 127.0.0.1:9090 发送 POST 数据:

name=RedHeart&age=18&gender=男

终端将输出如下内容:

[data] name=RedHeart&age=18&gender=%E7%94%B7
[POST] name=RedHeart&age=18&gender=%E7%94%B7

解析

在获取到完整的数据后,我们需要对数据进行解析。我们可以使用 NodeJS 的内置模块 querystring 来对 application/x-www-form-urlencoded 格式的字符串进行解析。

const express = require('express');
// 导入 querystring
const qs = require('querystring');

const app = express();
// 使服务器监听本机的 9090 端口
app.listen(9090);

// 在接收到客户端发送的数据后将触发 data
// 事件,获取的数据可以通过向该事件的回调函数传递
// 参数来进行获取.
function mw(req, res, next){  
    // 定义一个空字符串来对接收到的多段(可能)
    // 数据进行拼接.
    req.body = '';
    req.on('data', (chunk) => {
    	req.body += chunk;
    }) 

    req.on('end', () => {
        // 对客户端发送的完整数据进行解析
        req.body = qs.parse(req.body);

        // 将执行权交给下一个中间件或函数
        next();
    })
}

// 将自定义中间件注册为全局中间件
app.use(mw);

// 创建路由
app.post('/', (req, res, next) => {
	// 向服务器端发送响应数据
    res.send(req.body);
    console.log(req.body);
})

注:

qs.parse() 函数能够解析 application/x-www-form-urlencoded 格式的字符串并将其转换为 JavaScript 对象。

执行结果:

在执行上述代码后,如果你向 127.0.0.1:9090 发送 POST 数据:

name=RedHeart&age=18&gender=男

终端将输出如下内容:

[Object: null prototype] { name: ‘RedHeart’, age: ‘18’, gender: ‘男’ }

而此时,你将在服务器端接收到如下数据:

{“name”:“RedHeart”,“age”:“18”,“gender”:“男”}

这表明:

  1. 使用 req.send() 向客户端响应一个对象时,该对象将自动被转换为 JSON 格式的字符串。
  2. qs.parse() 函数具有对字符串进行 URL 解码 的功能。

将自定义中间件封装为模块

urlencoded.js

// 导入 querystring
const qs = require('querystring');

// 在接收到客户端发送的数据后将触发 data
// 事件,获取的数据可以通过向该事件的回调函数传递
// 参数来进行获取.
function mw(req, res, next){  
    // 定义一个空字符串来对接收到的多段(可能)
    // 数据进行拼接.
    req.body = '';
    req.on('data', (chunk) => {
    	req.body += chunk;
    }) 

    req.on('end', () => {
        // 对客户端发送的完整数据进行解析
        req.body = qs.parse(req.body);

        // 将执行权交给下一个中间件或函数
        next();
    })
}

// 将中间件函数进行导出
module.exports = mw;

index.js

const express = require('express');
// 导入自定义中间件
const mw = require('./urlencoded.js')

const app = express();
// 使服务器监听本机的 9090 端口
app.listen(9090);

// 将自定义中间件注册为全局中间件
app.use(mw);

// 创建路由
app.post('/', (req, res, next) => {
	// 向服务器端发送响应数据
    res.send(req.body);
    console.log(req.body);
})

在将自定义中间件封装为模块后,仅需要将该模块导入并将其注册为全局中间件即可。

如果各位发现了文章中出现的问题或有什么建议,请点明。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BinaryMoon

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值