最近遇到了一个奇怪的问题,关于body-parser
报错:
UnsupportedMediaTypeError: unsupported content encoding "utf-8"
at contentstream (C:\workspace\xxx\node_modules\body-parser\lib\read.js:181:13)
at read (C:\workspace\xxx\xxx\body-parser\lib\read.js:56:14)
at urlencodedParser (C:\workspace\xxx\node_modules\body-parser\lib\types\urlencoded.js:116:5)
at Layer.handle [as handle_request] (C:\workspace\xxx\node_modules\express\lib\router\layer.js:95:5)
at trim_prefix (C:\workspace\xxx\node_modules\express\lib\router\index.js:328:13)
at C:\workspace\xxx\node_modules\express\lib\router\index.js:286:9
at Function.process_params (C:\workspace\xxx\node_modules\express\lib\router\index.js:346:12)
at next (C:\workspace\xxx\node_modules\express\lib\router\index.js:280:10)
at jsonParser (C:\workspace\xxxt\node_modules\body-parser\lib\types\json.js:119:7)
at Layer.handle [as handle_request] (C:\workspace\xxx\node_modules\express\lib\router\layer.js:95:5)
于是开始了面向百度编程,查到了两种解决方案:
解决方案1:
const app = express();
// 加上下面的代码,一定要放在 `express()`下面第一句
app.use((req, res, next) => {
if (req.headers['content-encoding'] === 'utf-8') {
delete req.headers['content-encoding'];
}
next();
});
这种方式我不能说推荐,也不能说不推荐,因为这里只是过滤utf-8
,如果还有其他的怎么办?再写?至于为什么会有其他的等会再讨论,至于上面的代码什么意思等会在解读。
解决方案2:
修改node_modules/body-parser/lib/read.js
中的代码,在第181-184
行:
switch (encoding) {
case 'deflate':
stream = zlib.createInflate()
debug('inflate body')
req.pipe(stream)
break
case 'gzip':
stream = zlib.createGunzip()
debug('gunzip body')
req.pipe(stream)
break
case 'identity':
stream = req
stream.length = length
break
default:
// throw createError(415, 'unsupported content encoding "' + encoding + '"', {
// encoding: encoding,
// type: 'encoding.unsupported'
// })
// 注释上面的,修改成下面的
stream = req
stream.length = length
}
上面这种方式是能彻底根治这个问题的,比上面的方式要好,但是还是有多人协同的问题,版本升级的问题。
问题是解决了,但是看到这个文件也就208
行代码,我就读一下,为什么会出现这个问题,也顺便了解了第一种方式工作方式。
read.js
代码解读
其实这个代码很简单,至于这个问题的原因,主要是在下面的这个方法上面,也就是我们修改的代码:
/**
* Get the content stream of the request.
*
* @param {object} req
* @param {function} debug
* @param {boolean} [inflate=true]
* @return {object}
* @api private
*/
function contentstream (req, debug, inflate) {
// 获取请求头 content-encoding 的值,如果没有 encoding 的值就为 identity
var encoding = (req.headers['content-encoding'] || 'identity').toLowerCase()
// 获取请求头 content-length 的值
var length = req.headers['content-length']
var stream
// 判断 encoding 的值是什么,也就是 content-encoding 的值是什么,其实这下面的都是内容压缩的方式
switch (encoding) {
// deflate 压缩
case 'deflate':
stream = zlib.createInflate()
debug('inflate body')
req.pipe(stream)
break
// gzip 压缩
case 'gzip':
stream = zlib.createGunzip()
debug('gunzip body')
req.pipe(stream)
break
// 不做压缩
case 'identity':
stream = req
stream.length = length
break
// 其他的就报错了
default:
throw createError(415, 'unsupported content encoding "' + encoding + '"', {
encoding: encoding,
type: 'encoding.unsupported'
})
}
return stream
}
可以看到这里的重点就在于content-encoding
上面,content-encoding = 'utf-8'
,utf-8
不是编码格式的?怎么会有问题呢?百度都是修改这个工具包的源码,这工具有问题?那为啥这么多人在用,作者也不修改呢?一定有猫腻!!!
content-encoding
属性的含义
经过大量查询资料之后,发现content-encoding
的作用是标记消息体应该使用什么编码类型进行编码,以及编码的顺序,主要用于在不丢失原媒体类型内容的情况下压缩消息数据。
它还有一个配套属性accept-encoding
,它的作用是用来标识客户端能够理解的内容编码方式。
accept-encoding
的意思是:我只认识gzip
或者deflate
,表达为accept-encoding='gzip,deflate'
,你不要给我其他的;
content-encoding
的意思是:我现在给你数据是经过gzip
处理过的,你需要对数据进行处理一下才能认识,表达为content-encoding='gzip'
。
那么content-encoding='uft-8'
好像也破案了,我的内容是utf-8
的,你不用gbk
给我处理了,不然你不认识;
????????????????????????????????????????????????????
content-type
说你抢我的活?你干嘛呀!哎哟~
我也不逗机灵了,下面的是mdn
上面的描述
实体消息首部 (en-US)
Content-Encoding
列出了对当前实体消息(消息荷载)应用的任何编码类型,以及编码的顺序。它让接收者知道需要以何种顺序解码该实体消息才能获得原始荷载格式。 Content-Encoding 主要用于在不丢失原媒体类型内容的情况下压缩消息数据。请注意原始媒体/内容的类型通过
Content-Type
首部给出,而Content-Encoding
应用于数据的表示,或“编码形式”。如果原始媒体以某种方式编码(例如 zip 文件),则该信息不应该被包含在Content-Encoding
首部内。
第二句就一下描述出问题了,所以是很多库的作者自己弄错了含义,给我们带来一大堆的问题,关键是很多库现在的体量已经很大了,就比如我遇到这个问题的,和我对接的程序是Java
写的,具体的我不知道用的什么,然后网上也看到了很多库都无法修改这个属性值。
结束
不知道怎么评价这个bug,你说是bug吧,库的作者确实是按照规范来做的,你说不是bug吧,可以上升到线上重大故障。
上面的方案推荐第一种,因为改源码确实会有很多问题,除非你自己去维护这个代码,不到万不得已不建议修改源码。