本文节选自《Nodejs学习笔记》,更多章节及更新请访问github主页地址。欢迎进群交流,群号197339705。前面写的body-parser是一个很常用的express中间件,用来解析post请求的requestbody。使用起来非常简单,下面两行代码已经涵盖了大部分的使用场景。app.use(bodyParser.json());app.use(bodyParser.urlencoded({extended:false}));本文从一个简单的例子入手,探究body-parser的内部实现。至于如何使用body-parser,有兴趣的同学可以参考官方文档。在正式讲解入门基础知识之前,我们先来看一个POST请求报文,如下图。POST/testHTTP/1.1Host:127.0.0.1:3000Content-Type:text/plain;charset=utf8Content-Encoding:gzipchyingp其中需要注意Content-Type、Content-Encoding和消息体:Content-Type:request消息体的类型和编码。常见类型有text/plain、application/json、application/x-www-form-urlencoded。常见的编码有utf8、gbk等。Content-Encoding:声明消息体的压缩格式。常用值有gzip、deflate和identity。消息体:这里是一个普通的文本字符串chyingp。body-parser主要做什么?body-parser的要点如下:处理不同类型的请求体:如text、json、urlencoded等,对应的消息体格式不同。处理不同的编码:如utf8、gbk等。处理不同的压缩类型:如gzip、deflare等。其他边界和异常的处理。1.处理不同类型的请求体为了方便读者测试,下面的例子包括服务端和客户端代码,完整代码可以在作者的github上找到。解析text/plain客户端请求的代码如下,使用默认编码,不压缩请求体。请求正文类型为文本/纯文本。varhttp=require('http');varoptions={hostname:'127.0.0.1',port:'3000',path:'/test',method:'POST',headers:{'Content-Type':'text/plain','Content-Encoding':'identity'}};varclient=http.request(options,(res)=>{res.pipe(process.stdout);});client.end('清普');服务器代码如下。text/plain类型的处理比较简单,就是buffer的拼接。varhttp=require('http');varparsePostBody=function(req,done){vararr=[];变量块;req.on('data',buff=>{arr.push(buff);});req.on('end',()=>{chunks=Buffer.concat(arr);done(chunks);});};varserver=http.createServer(function(req,res){parsePostBody(req,(chunks)=>{varbody=chunks.toString();res.end(`你的昵称是${body}`)});});server.listen(3000);解析application/json客户端代码如下,将Content-Type换成application/json。varhttp=require('http');varquerystring=require('querystring');varoptions={主机名:'127.0.0.1',端口:'3000',路径:'/test',方法:'POST',headers:{'Content-Type':'application/json','Content-Encoding':'identity'}};varjsonBody={nick:'chyingp'};varclient=http.request(options,(res)=>{res.pipe(process.stdout);});client.end(JSON.stringify(jsonBody));服务端代码如下,相比text/plain,只多了一个JSON.parse()过程。varhttp=require('http');varparsePostBody=function(req,done){varlength=req.headers['content-length']-0;变量arr=[];变量块;req.on('数据',buff=>{arr.push(buff);});req.on('end',()=>{chunks=Buffer.concat(arr);done(chunks);});};varserver=http.createServer(function(req,res){parsePostBody(req,(chunks)=>{varjson=JSON.parse(chunks.toString());//关键代码res.end(`你的昵称是${json.nick}`)});});server.listen(3000);解析application/x-www-form-urlencoded客户端代码如下,其中请求body通过querystring格式化得到类似nick=chyingp的字符串。varhttp=require('http');varquerystring=require('querystring');varoptions={主机名:'127.0.0.1',端口:'3000',路径:'/test',方法:'POST',headers:{'Content-Type':'form/x-www-form-urlencoded','Content-Encoding':'identity'}};varpostBody={nick:'chyingp'};varclient=http.request(options,(res)=>{res.pipe(process.stdout);});client.end(querystring.stringify(postBody));服务端代码如下,类似于text/plain的解析,只是多了一个querystring.parse()的调用。varhttp=require('http');varquerystring=require('querystring');varparsePostBody=function(req,done){varlength=req.标题['内容长度']-0;变量arr=[];变量块;req.on('data',buff=>{arr.push(buff);});req.on('end',()=>{chunks=Buffer.concat(arr);done(chunks);});};varserver=http.createServer(function(req,res){parsePostBody(req,(chunks)=>{varbody=querystring.parse(chunks.toString());//keyCoderes.end(`Yournickis${body.nick}`)});});服务器.listen(3000);2.处理不同的编码很多时候,客户端的请求不一定使用默认的utf8编码,这时候就需要对请求体进行解码。客户端请求如下,主要有两点。编码声明:在Content-Type末尾添加;charset=gbk。请求体编码:这里使用iconv-lite对请求体进行编码。iconv.encode('程序员卡',encoding)varhttp=require('http');variconv=require('iconv-lite');varencoding='gbk';//请求编码varoptions={hostname:'127.0.0.1',port:'3000',path:'/test',method:'POST',headers:{'Content-Type':'text/plain;charset='+encoding,'Content-Encoding':'identity',}};//注意:nodejs本身不支持gbk编码,所以在发送请求之前,需要进行编码varbuff=iconv.encode('程序员卡',encoding);varclient=http.request(options,(res)=>{res.pipe(process.stdout);});client.end(buff,encoding);服务端代码如下,这里多了两步:编码判断和解码操作。先通过Content-Type获取编码类型gbk,再通过iconv-lite进行反解码操作。varhttp=require('http');varcontentType=require('content-type');variconv=require('iconv-lite');varparsePostBody=function(req,done){varobj=contentType.parse(req.headers['内容类型']);varcharset=obj.parameters.charset;//编码判断:这里得到的值为'gbk'vararr=[];变量块;req.on('数据',buff=>{arr.push(buff);});req.on('end',()=>{chunks=Buffer.concat(arr);varbody=iconv.decode(chunks,charset);//解码完成(body);});};varserver=http.createServer(function(req,res){parsePostBody(req,(body)=>{res.end(`你的昵称是${body}`)});});服务器.listen(3000);3.处理不同的压缩类型下面是一个gzip压缩的例子。客户端代码如下,要点如下:压缩类型声明:Content-Encoding赋值为gzip。请求体压缩:gzip通过zlib模块对请求体进行压缩。varhttp=require('http');varzlib=require('zlib');varoptions={主机名:'127.0.0.1',端口:'3000',路径:'/test',方法:'POST',headers:{'Content-Type':'text/plain','Content-Encoding':'gzip'}};varclient=http.request(options,(res)=>{res.pipe(process.stdout);});//注意:当Content-Encoding设置为gzip时,发送到服务器的数据也应该是gzipvarbuff=zlib.gzipSync('chyingp');client.end(buff);服务器代码如下,zlib模块用于解压请求体(guzip)。varhttp=require('http');varzlib=require('zlib');varparsePostBody=function(req,done){varlength=req.标题['内容长度']-0;varcontentEncoding=req.headers['content-encoding'];变量流=请求;//关键代码如下if(contentEncoding==='gzip'){stream=zlib.createGunzip();请求管道(流);}vararr=[];变量块;stream.on('data',buff=>{arr.push(buff);});stream.on('end',()=>{chunks=Buffer.concat(arr);done(chunks);});stream.on('error',error=>console.error(error.message));};varserver=http.createServer(function(req,res){parsePostBody(req,(chunks)=>{varbody=chunks.toString();res.end(`你的昵称是${body}`)});});服务器.listen(3000);编写body-parser的核心实现并不复杂。查看源码后,你会发现更多的代码是在处理异常和边界。另外,对于POST请求,还有一个很常见的Content-Type就是multipart/form-data。这个的处理比较复杂,body-parser不打算支持。篇幅有限,后续章节会继续展开。欢迎交流,如有错误或遗漏请指出。相关链接https://github.com/expressjs/...https://github.com/ashtuchkin...
