• 使用node.js的bodyParser中间件读取post数据解析


    昨天我们使用的网关转发数据时出了点问题!

    情景是这样的,另一方以Post的形式向我的node.js服务推送JSON数据。但是使用bodyParser中间件后,在req.body中拿不到任何信息。

    代码如下:

        app.use(bodyParser.json());
                     app.post('/api/*',function(req,res){
                                 _option.onMessage({url:req.url,query:req.query,body:req.body},function(error,data){
                                 res.header('Access-Control-Allow-Origin','*');
                                 res.header('Access-Control-Allow-Headers','X-Requested-With, Content-Type');
                                 console.log(req.params);console.log(req.body.data);
                                 res.send(data);
                           });

     当时百思不得其解,后来发现推送方的Post请求中请求头的Content-Type的值为"text/xml"。当时觉得和这个值有很大的关系,经过检测果然不出所料。

    自己模拟一个Post请求时将Content-type设置为"application/json"那么传过来的JSON数据就可以通过bodyParser中间件后由req.body拿到。但是另一方并没有以Content-type="application/json"的形式进行传送,我们只能从自己的node.js服务下手,坚信一定会想到办法。

    在网上大概找了30多分钟的资料但是没有任何眉目!!!   无奈之下只能自己读bodyParser源码,而且我始终相信读别人的代码是最快的进步方式。

    果然功夫不负有心人,在长达半个多小时与bodyParser源码的”作战中“,找到了解决问题的方法!

    源代码分析如下:

    bodyParser一定是对Content-Type进行了校验,那么我们就从这里入手,首先找到了bodyParser.json方法,代码如下:

    function json(options) {
      options = options || {}
    
      var limit = typeof options.limit !== 'number'
        ? bytes(options.limit || '100kb')
        : options.limit
      var inflate = options.inflate !== false
      var reviver = options.reviver
      var strict = options.strict !== false
      var type = options.type || 'json'
      var verify = options.verify || false
    
      if (verify !== false && typeof verify !== 'function') {
        throw new TypeError('option verify must be function')
      }
    
      function parse(body) {
        if (body.length === 0) {
          // special-case empty json body, as it's a common client-side mistake
          // TODO: maybe make this configurable or part of "strict" option
          return {}
        }
    
        if (strict) {
          var first = firstchar(body)
    
          if (first !== '{' && first !== '[') {
            throw new Error('invalid json')
          }
        }
    
        return JSON.parse(body, reviver)
      }
    
      return function jsonParser(req, res, next) {
        if (req._body) return next()
        req.body = req.body || {}
    
        if (!typeis(req, type)) return next()
    
        // RFC 7159 sec 8.1
        var charset = typer.parse(req).parameters.charset || 'utf-8'
        if (charset.substr(0, 4).toLowerCase() !== 'utf-') {
          var err = new Error('unsupported charset')
          err.status = 415
          next(err)
          return
        }
    
        // read
        read(req, res, next, parse, {
          encoding: charset,
          inflate: inflate,
          limit: limit,
          verify: verify
        })
      }
    }

    那么上述代码的 if (!typeis(req, types_))则为关键点,如果if执行了next()那下面的语句就将无法执行,我们也就无法进行read方法中,所以这个typeis方法即为突破口。

    而typeis方法的位置在type-is模块中,关键代码如下:

    function typeis(value, types_) {
      var i
      var types = types_
    
      // remove parameters and normalize
      value = typenormalize(value)
    
      // no type or invalid
      if (!value) {
        return false
      }
    
      // support flattened arguments
      if (types && !Array.isArray(types)) {
        types = new Array(arguments.length - 1)
        for (i = 0; i < types.length; i++) {
          types[i] = arguments[i + 1]
        }
      }
    
      // no types, return the content type
      if (!types || !types.length) return value;
    
      var type
      for (i = 0; i < types.length; i++) {
        if (mimeMatch(normalize(type = types[i]), value)) {
          return type[0] === '+' || ~type.indexOf('*')
            ? value
            : type
        }
      }
    
      // no matches
      return false;
    }
    ................
    function typenormalize(value) {
      try {
        var type = typer.parse(value)
        delete type.parameters
        return typer.format(type)
      } catch (err) {
        return null
      }
    }

    上述代码的value实则为传进来的req对象,也就是请求对象。那么请求对象通过'media-typer'模块中的parse方法将req判断为对象类型。代码如下:

    function parse(string) {
      if (!string) {
        throw new TypeError('argument string is required')
      }
    
      // support req/res-like objects as argument
      if (typeof string === 'object') {
        string = getcontenttype(string)
      }
    
      if (typeof string !== 'string') {
        throw new TypeError('argument string is required to be a string')
      }
    .................................
    
    function getcontenttype(obj) {
      if (typeof obj.getHeader === 'function') {
        // res-like
        return obj.getHeader('content-type')
      }
    
      if (typeof obj.headers === 'object') {
        // req-like
        return obj.headers && obj.headers['content-type']
      }
    }

    之后在通过getcontenttype方法取出content-type的值将其返回,最后由typeis方法中的value接收。在红色的for循环中可以看出typeis函数中的types_可以为数组类型,并且会循环与value进行匹配,如果相同则直接返回。那么返回之后就不会再去执行next(),这样我们就可以继续执行下面的代码,我们就可以利用bodyParser中间件从req.body中获取数据了至于types_数组中写几种content-type值就由你的需要决定!

    解决问题代码例如:

    app.use(bodyParser.json( { type: ['text/xml','application/json'] } ));
                     app.post('/api/*',function(req,res){
                                 _option.onMessage({url:req.url,query:req.query,body:req.body},function(error,data){
                                 res.header('Access-Control-Allow-Origin','*');
                                 res.header('Access-Control-Allow-Headers','X-Requested-With, Content-Type');
                                 console.log(req.params);console.log(req.body.data);
                                 res.send(data);
                           });
  • 相关阅读:
    SBIT
    Linux 系统中进程5中常见状态
    centos yum command
    About DNS
    【从零开始学BPM,Day1】工作流管理平台架构学习
    打破陈规抓痛点,H3 BPM10.0挑战不可能
    H3 BPM让天下没有难用的流程之产品概述
    《中国BPM品牌竞争力指数》完整版
    H3 BPM的品牌制胜之道
    《中国BPM品牌竞争力指数》报告出炉,H3 BPM持续领跑
  • 原文地址:https://www.cnblogs.com/zzq-include/p/4886723.html
Copyright © 2020-2023  润新知