阅读原序Express是一个基于NodeJS平台的Web框架,应用非常广泛。在Express社区中,大量的开发者通过Express中间件的特性开发出了各种功能的中间件,用于处理一些响应,为请求对象req和响应对象res添加属性或方法。下面我们将通过分析常用的body-parser中间件的原理来学习如何开发Express中间件。想了解更多快递请看《Express 源码分析及简易封装》内包装原理。body-parser的基本使用如果要分析一个中间件的原理,应该从使用入手,在对使用有充分了解的基础上进行分析。现在搭建一个简单的Express服务,使用body-parser中间件,使用前需要先安装。npminstallexpressbody-parser使用body-parser代码如下:constexpress=require("express");constbodyParser=require("body-parser");//创建一个服务constapp=express();//使用body-parser中间app.use(bodyParser.urlencoded({extended:true}));app.use(bodyParser.json());//创建路由app.post("/login",function(req,res){console.log(req.body);res.send(req.body);});//监控服务app.listen(3000,function(){console.log("serverstart3000");});启动上述服务器,使用postman工具分别通过表单提交和json格式访问http://localhost:3000/login,查看服务器控件后台的打印结果和postman的返回结果。body-parser的实现一、原理分析从上面的用例,我们可以分析出以下几点:首先,body-parser中间件的作用是给req添加属性body,value是一个对象,request是以键值对的形式存储其次,body-parser只处理POST请求;最后,body-parser模块导出了一个对象,该对象有两个方法urlencoded和json,分别处理表单提交和请求body参数的json格式。2、分析urlencoded和json的公共逻辑。在实现之前,我们先来分析一下这两个方法。首先,我们需要读取请求体中的内容。数据传输的类型是Buffer。转换为字符串后,会导致不同的提交方式。请求体中的内容是querystring和jsonstring的区别。解析失败时需要进行错误处理,其他中间件在不是POST请求时需要向下执行,核心的是将请求体中的数据转换成对象挂在req.body上。唯一的区别是使用的转换数据的方法。两者之间唯一的区别是请求头Content-Type的值,所以我们可以提取所有公共逻辑并使用acceptPost函数执行它。3.模块创建让我们创建自己的body-parser模块来防止命名冲突。我们的模块名为my-body-parser。处理参数需要用到querystring和qs两个模块,其中qs是第三方模块。使用前需要安装。npminstallqsqs和querystring的功能基本相同,都是处理querystring格式的参数,但也有一点小区别,querystring只能处理一级,而qs可以处理多级。constquerystring=require("querystring");constqs=require("qs");//urlencoded和json的共同逻辑functionacceptPost(){//...}//处理表单提交的方法functionurlencoded(){//...}//处理请求体json的方法functionjson(){//...}//导出对象module.exports={urlencoded,json};构建完基础模块后,我们将实现body-parser模块中的公共逻辑函数acceptPost。4.为了兼容urlencoded方法和json方法,acceptPost的实现设计了两个参数,一个是区分当前调用方法的类型,一个是urlencoded方法的options。//acceptPost的实现//urlencoded方法和json方法的公共逻辑函数functionacceptPost(type,options){//返回一个中间件函数returnfunction(req,res,next){//获取请求头letcontentType=req.标头[“内容类型”];//判断请求头是否满足两次提交,则直接交给其他中间件处理/json"){//存储数据的数组letbuffers=[];req.on("data",function(data){//接收数据并存入数组buffers.push(data);});req.on("end",function(){//合并数据并转化为字符串letresult=Buffer.concat(buffers).toString();//处理数据并挂载到req.bodyproperty//如果是表单提交,则使用querystring或qs,否则使用JSON.parseif(type==="form"){//如果配置扩展值为true,则使用qs,否则使用querystringreq.body=options.extended?qs.parse(result):querystring.parse(结果);}elseif(type==="json"){req.body=JSON.parse(result);}下一个();//向下执行});//错误处理req.on("err",function(err){next(err);});}else{下一个();}}}5.urlencoded和json方法的实现//处理表单提交的方法functionurlencoded(options){//定义类型值lettype="form";returnacceptPost(type,options)}//处理请求体的方法jsonfunctionjson(){//定义类型值lettype="json";returnacceptPost(type);}当我们抽取所有的公共逻辑后,发现urlencoded和json方法只需要定义不同的类型就可以执行各自的中间件逻辑。以上对body-parse中间件原理的分析,目的在于了解Express中间件的开发模式。在这里总结一下,Express中间件返回一个带有参数req、res和next的函数。当函数无法处理某些情况时,需要调用next。发生错误时,将调用next并传递错误,交给Express内置的错误处理中间件处理。当中间件内部代码涉及到异步操作时,必须在异步完成回调中调用next。这一点没有Koa方便,这也是两者的区别,因为在Koa中大量使用Async/await来让await同时执行异步代码。
