学习Node js:raw-body模块源码解析

2023-09-17 18:36:25

raw-body是什么

raw-body的主要功能是处理HTTP请求体的原始数据。它提供了以下核心功能:

  1. 解析请求体:可以从HTTP请求中提取原始数据,包括文本和二进制数据。
  2. 配置选项:通过配置项,可以设置请求体的大小限制、编码方式等参数。
  3. 异常处理:模块能够处理异常情况,如请求体超出限制。
  4. 编码转换:支持将原始数据解码为指定编码的字符串,或者返回Buffer实例。

express中的body-parser中间件就使用了raw-body来处理请求

raw-body基础用法

安装:

npm install raw-body

引入:

var getRawBody = require('raw-body')

getRawBody函数签名如下:

getRawBody(stream, [options], [callback])

stream是需要解析的流。

options是一些配置项。

  • length - 流的长度。
  • limit - 请求体的大小限制。比如 1000'500kb''3mb'
  • encoding - 用于将请求体解码为字符串的编码。默认情况下,如果未指定编码,将返回 Buffer 实例。最有可能的是,您需要 utf-8 ,因此将 encoding 设置为 true 将解码为 utf-8

callback是解析完成之后的回调函数。

结合express一起使用的例子如下:


var contentType = require('content-type')
var express = require('express')
var getRawBody = require('raw-body')

var app = express()

app.use(function (req, res, next) {
  getRawBody(req, {
    length: req.headers['content-length'],
    limit: '1mb',
    encoding: contentType.parse(req).parameters.charset
  }, function (err, string) {
    if (err) return next(err)
    req.text = string
    next()
  })
})

// 可以在后续的express中间件中访问 req.text

也可以使用promise风格调用getRawBody

var getRawBody = require('raw-body')
var http = require('http')

var server = http.createServer(function (req, res) {
  getRawBody(req)
    .then(function (buf) {
      res.statusCode = 200
      res.end(buf.length + ' bytes submitted')
    })
    .catch(function (err) {
      res.statusCode = 500
      res.end(err.message)
    })
})

server.listen(3000)

raw-body v0.0.3源码阅读

我们选择的版本是v0.0.3,选择这个版本的原因非常简单:代码量非常少,只有70行

https://raw.githubusercontent.com/AC-greener/blog-image/main/Untitled.png

  1. 主要作用是处理一些异常,当请求体内容超过限制时会调用stream.resume这个方法销毁这个stream,防止请求数据被缓冲。
  2. 监听stream的一些事件,然后使用回调进行处理,stream之所以能调用ononceremoveListener等方法,是因为stream继承了Nodejs中的EventEmitter模块。
    1. data事件:每当可读流接收到新的数据块时,就会触发data事件。一般用于逐块处理请求体数据。
    2. end事件:end事件在可读流读取完数据后触发,表示数据流结束。
    3. error事件:当可读流发生错误时触发error事件。
    4. close事件:close事件在可读流关闭时触发,表示流已经被关闭,用于在流关闭时进行一些资源清理或收尾工作

onData

https://raw.githubusercontent.com/AC-greener/blog-image/main/20230915223514.png

onData的核心代码只有这两行:

吧收到的chunk 放到buffers数组里面,chunk的数据类型默认是Buffer类型

然后使用 chunk.length 返回当前chunk的字节数,并累加起来

onEnd

https://raw.githubusercontent.com/AC-greener/blog-image/main/20230915223614.png

onEnd的核心是57行:调用传入的回调函数,并吧Buffer.concat的结果传入

Buffer.concat方法会吧 buffers 中的所有 Buffer 实例连接在一起,返回一个新的 Buffer

cleanup

https://raw.githubusercontent.com/AC-greener/blog-image/main/20230915223806.png

cleanup中的主要逻辑就是调用removeListener对请求数据流的事件监听器进行清理,可以防止内存泄漏以及不必要的资源占用。

v0.0.3版本的代码看完之后我们再看看看v2.5.2的

raw-body v2.5.2源码阅读

2.5.2版本的代码有300多行,是0.0.3版本的四倍,不过核心功能是差不多的,差异点如下:

  • options配置项新增了encoding参数:用于吧body解码成指定编码的字符串,默认情况下,如果没有指定编码,将返回一个 Buffer 实例,
  • stream增加了aborted事件的处理:stream.on('aborted', onAborted)

raw-body依赖的模块

2.5.2版本依赖了5个npm模块

https://raw.githubusercontent.com/AC-greener/blog-image/main/20230915223839.png

先看一下这些模块的功能:

  • bytes 是一个用于在不同单位之间进行字节转换的Nodejs模块。常用方法如下:
bytes.parse('1KB');// output: 1024
bytes.format(1024);// output: '1KB'
  • http-errors 用于创建HTTP错误对象。它简化了处理HTTP请求时生成错误响应的过程。也可以和ExpressKoaConnect一起使用。用法如下:
var createError = require('http-errors')
var express = require('express')
var app = express()

app.use(function (req, res, next) {
  if (!req.user) return next(createError(401, 'Please login to view this page.'))
  next()
})
  • iconv-lite 用于处理字符编码的转换。可以在不同的字符编码之间进行转换。
const iconv = require('iconv-lite');
const originalText = '你好,世界!';

// 将文本编码为 Buffer
const encodedBuffer = iconv.encode(originalText, 'utf-8'); 

// 将编码后的 Buffer 解码为文本
const decodedText = iconv.decode(encodedBuffer, 'utf-8');
  • unpipe 用于取消可读流(Readable Stream)和可写流(Writable Stream)之间的数据传输。例如从文件读取流到HTTP响应流。unpipe 库允许你取消这种数据传输。

getRawBody是入口函数,我们一起看一下:

https://raw.githubusercontent.com/AC-greener/blog-image/main/20230915223911.png

  1. 针对参数做一些验证以及错误处理
  2. 调用bytes模块的parse方法解析传入的limit参数
  3. 调用readStream函数处理stream,这里做了判断,如果传入了回调函数,则使用回调的方式传递解析之后stream,否则使用promise风格来处理

readStream函数和v0.0.3版本的代码变化不太大:

https://raw.githubusercontent.com/AC-greener/blog-image/main/20230915224042.png

  1. 调用getDecoder函数,用于获取指定编码的解码器。而getDecoder函数里面又调用了iconv模块的getDecoder方法
  2. 监听streamaborted事件,当客户端中止 HTTP 请求时,可读流会触发 aborted 事件。比如在请求尚未完成时客户端提前关闭了连接。
    onAborted函数如下:

https://raw.githubusercontent.com/AC-greener/blog-image/main/20230915224127.png

核心逻辑就是调用createError创建一个错误信息,然后调用done函数。

done函数是readStream里面需要重点关注的函数,代码如下:

https://raw.githubusercontent.com/AC-greener/blog-image/main/20230916130346.png

1,将complete标记为true,表示这个流已经处理完了。

2,判断done函数的调用环境,如果是在同步代码块,则使用process.nextTick延迟invokeCallback函数的调用。
done函数同步调用是在这几个地方:

https://raw.githubusercontent.com/AC-greener/blog-image/main/20230915224353.png

异步调用则是在stream.on事件的几个回调函数中,并在212行标记为异步:

https://raw.githubusercontent.com/AC-greener/blog-image/main/20230915224423.png

3,根据done函数第一个参数判断是否有错误,如果有错误则调用halt方法处理streamhalt 方法会提前结束stream的读取操作。

halt函数内容如下:

https://raw.githubusercontent.com/AC-greener/blog-image/main/20230915224452.png

unpipe(stream) 会断开与这个流相关的其他管道,如果有其他流正在处理 stream 输出的数据,这些流不会收到来自 stream 的数据。

pause 方法是 Nodejs 可读流的一个方法,用于将流暂停,停止触发 data 事件,不再传递数据。

总结

本文我们了解了raw-body的简单使用,raw-body模块也是Nodejs生态中使用的很频繁的一个模块,通过对v0.0.3和v2.5.2版本源码的解析,也了解了内部实现。

参考资料

https://github.com/stream-utils/raw-body

https://nodejs.cn/dist/latest-v18.x/docs/api/stream.html

https://nodejs.cn/dist/latest-v18.x/docs/api/buffer.html
在这里插入图片描述

更多推荐

基于Android+OpenCV+CNN+Keras的智能手语数字实时翻译——深度学习算法应用(含Python、ipynb工程源码)+数据集(二)

目录前言总体设计系统整体结构图系统流程图运行环境模块实现1.数据预处理2.数据增强3.模型构建1)定义模型结构2)优化损失函数相关其它博客工程源代码下载其它资料下载前言本项目依赖于Keras深度学习模型,旨在对手语进行分类和实时识别。为了实现这一目标,项目结合了OpenCV库的相关算法,用于捕捉手部的位置,从而能够对视

全国职业技能大赛云计算--高职组赛题卷①(私有云)

全国职业技能大赛云计算--高职组赛题卷①(私有云)第一场次题目:OpenStack平台部署与运维任务1基础运维任务(5分)任务2OpenStack搭建任务(15分)任务3OpenStack云平台运维(15分)任务4OpenStack云平台运维开发(15分,本任务只公布考试范围,不公布赛题)需要环境私信博主!!!第一场次

基于罪名法务智能知识图谱(含码源):基于280万罪名预测、20W法务问答与法律资讯问答功能

项目设计集合(人工智能方向):助力新人快速实战掌握技能、自主完成项目设计升级,提升自身的硬实力(不仅限NLP、知识图谱、计算机视觉等领域):汇总有意义的项目设计集合,助力新人快速实战掌握技能,助力用户更好利用CSDN平台,自主完成项目设计升级,提升自身的硬实力。专栏订阅:项目大全提升自身的硬实力[专栏详细介绍:项目设计

Linux内核源码分析 (B.3) 深入理解 Linux 物理内存分配全链路实现

Linux内核源码分析(B.3)深入理解Linux物理内存分配全链路实现文章目录Linux内核源码分析(B.3)深入理解Linux物理内存分配全链路实现@[toc]前文回顾1\.内核物理内存分配接口2.规范物理内存分配行为的掩码gfp\_mask3\.物理内存分配内核源码实现3.1内存分配行为标识掩码ALLOC\_\*

利用LSTM和TensorFlow模拟任意艺术家风格生成新歌词:完整Python实现指南

1.介绍随着深度学习技术的快速进步,我们现在可以使用各种神经网络结构来生成文本、图像甚至音乐。其中,长短期记忆网络(LSTM)是处理序列数据,如文本和时间序列数据的首选技术。在这篇文章中,我们将探讨如何使用LSTM和TensorFlow库来模拟任意艺术家的风格生成新歌词。2.LSTM网络简介长短期记忆网络(LSTM)是

LuatOS-SOC接口文档(air780E)--eink - 墨水屏操作库

常量常量类型解释eink.MODEL_1in02dnumber1.02寸deink.MODEL_1in54number1.54寸eink.MODEL_1in54_V2number1.54寸_V2eink.MODEL_1in54bnumber1.54寸beink.MODEL_1in54b_V2number1.54寸b_V

Node.js 20 —— 几个令人大开眼界的特性

前言:欢迎来到Node.js20Node.js20已经发布,带来了创新和激动人心的新时代。这个开创性的版本于2023年4月18日首次亮相,并将在2023年10月发布长期支持(LTS)版本,并且将持续支持至2026年4月,下面小编就为大家介绍一下Node.js20的几个新特性:1.Node.js权限访问Node.js20

JS案例:在浏览器实现自定义菜单

目录前言设计思路BaseElemMenuCustomElementBaseDragDragResize最终效果总结相关代码前言分享一下之前公司实现自定义菜单的思路,禁用浏览器右键菜单,使用自定义的菜单将其代替,主要功能有:鼠标右键调出菜单,双击选中/取消选中标签,新建标签,删除标签,调整位置,调整大小,取消拖拽,关闭菜

详解JS中常见的5 种 for 循环

for循环在平时开发中使用频率最高的,前后端数据交互时,常见的数据类型就是数组和对象,处理对象和数组时经常使用到for遍历,因此需要彻底搞懂这5种for循环。它们分别为:forfor...infor...offorawait..offorEachmap一、各个for介绍1、forfor循环是出现最早,也是应用最普遍的一

联表查询 && 索引 && 事务 && JDBC使用 &&CPU工作原理 && 线程概念 && Thread类的用法

第1题(单选题)题目名称:已知表T1中有2行数据,T2中有3行数据,执行SQL语句,“selecta.*fromT1a,T2b”后,返回的行数为题目内容:A.2B.3C.5D.6第2题(单选题)题目名称:Mysql查询时,只有满足联接条件的记录才包含在查询结果,这种联接是题目内容:A.左联接B.右联接C.内联接D.全联

Vue.js和TypeScript:如何完美结合

🌷🍁博主猫头虎(🐅🐾)带您GotoNewWorld✨🍁🦄博客首页——🐅🐾猫头虎的博客🎐🐳《面试题大全专栏》🦕文章图文并茂🦖生动形象🐅简单易学!欢迎大家来踩踩~🌺🌊《IDEA开发秘籍专栏》🐾学会IDEA常用操作,工作效率翻倍~💐🌊《100天精通Golang(基础入门篇)》🐅学会Gol

热文推荐