同时使用bodyParser.json()和bodyParser.urlencode()结果post请求得到504错误

需求:本地post请求到远程服务器http://www.imooc.com/data/che...,验证并返回html内容
问题:发现一直报错504,检查了很久,发现把bodyParser.urlencoded()注释掉,或者调整下app.use(bodyParser.json())和app.use(bodyParser.urlencoded({extended:true}))到代理后都可以获取到数据。
不明白,为什么bodyParser.urlencoded在代理前会有影响?我一开始在想是不是请求体的格式有问题。
客户端:

==html===
extends ../layout 
block content
    .col-md-3.col-md-offset-3
        .form-group.form-inline
            label(for="") 检验数字的奇偶性:
            button.btn.btn-success#load 检验
        .form-group.form-inline
            label(for="") 请输入一个数字:
            input#inputNumber.form-control
        ul#infoList
    script(src="/js/check_f.js")
    
===js=====
$(function () {
    $("#load").bind("click", function () {
        var $this = $(this);
        //借助本地的代理服务器向目标服务器发送数据
        $.post("http://localhost:3000/data/check_f.php", {
            num: $("#inputNumber").val()
        },
            function (data) {
                $("#infoList").append("<li>你输入的数字 <b>" + $("#inputNumber").val() + "</b>是<b>" + data + "</b></li>");
            })
    });
})

预期效果:
图片描述

出问题前:

服务器端:

/**
*解析请求的消息体
*/
var bodyParser = require('body-parser');
app.use(bodyParser.json());//返回一个只解析json的中间件,最后保存的数据都放在req.body对象上
app.use(bodyParser.urlencoded({ extended: true }));//返回的对象为任意类型
/**
 * proxy代理
 */
var proxy = require('http-proxy-middleware');//引入代理中间件
var dataProxy = proxy('/data', { target: "http://www.imooc.com/", changeOrigin: true });//将服务器代理到http://www.imooc.com上,本地服务器为localhost:3000
app.use('/data/*', dataProxy);//data子目录下的都是用代理
console.log("dataProxy->next");

为什么会有冲突呢?

===========================分割线=========================
了解了app.use,代理作用,以及在bodyParser.json,bodyParser.urlencode,http-proxy-middleware中加了debug code后,知道了原因。
在bodyParser组件的json.js,urlencode,read.js中加debug code,在http-proxy-middleware组件index.js中debug code.
json.js:

function json(options) {
  var opts = options || {}

    .....//省略
  //June[debug]
  console.log("json-type:"+opts.type);
  console.log("json-verify:"+opts.verify);
  var type = opts.type || 'application/json'
  var verify = opts.verify || false

    .....//省略
  console.log('shouldParse-typeof type:'+(typeof type));
  // create the appropriate type checking function
  var shouldParse = typeof type !== 'function'
    ? typeChecker(type)
    : type

    .....//省略

  return function jsonParser(req, res, next) {
    if (req._body) {
    //June[debug]
    console.log("json-:body already parsed");
      return debug('body already parsed'), next()
    }

    req.body = req.body || {}

    // skip requests without bodies
    if (!typeis.hasBody(req)) {
     //June[debug]
     console.log("json-:skip empty body");        
      return debug('skip empty body'), next()
    }

    debug('content-type %j', req.headers['content-type'])

    // determine if request should be parsed
    if (!shouldParse(req)) {
     //June[debug]
     console.log("json-:"+req); 
     console.log("json-:skip parsing");         
      return debug('skip parsing'), next()
    }

    // assert charset per RFC 7159 sec 8.1
    var charset = getCharset(req) || 'utf-8'
    if (charset.substr(0, 4) !== 'utf-') {
     //June[debug]
     console.log("json-:invalid charset");       
    .....//省略
    }
     //June[debug]
     console.log("json-:go to read");   
    // read
    read(req, res, next, parse, debug, {
    .....//省略
    })
  }
}

urlencode.js:


function urlencoded(options) {
  var opts = options || {}
    .....//省略
    //June[debug]
  console.log("urlencode:"+opts.type);
  var type = opts.type || 'application/x-www-form-urlencoded'
  var verify = opts.verify || false
    .....//省略
console.log('urlencode-typeof type:'+(typeof type));
  // create the appropriate type checking function
  var shouldParse = typeof type !== 'function'
    ? typeChecker(type)
    : type

    .....//省略
  return function urlencodedParser(req, res, next) {
    if (req._body) {
      //June[debug]
      console.log('body already parsed');
      return debug('body already parsed'), next()
    }

    req.body = req.body || {}

    // skip requests without bodies
    if (!typeis.hasBody(req)) {
      //June[debug]
      console.log('skip empty body');
      return debug('skip empty body'), next()
    }

    debug('content-type %j', req.headers['content-type'])

    // determine if request should be parsed
    if (!shouldParse(req)) {
      //June[debug]
      console.log('skip parsing');
      return debug('skip parsing'), next()
    }

    // assert charset
    var charset = getCharset(req) || 'utf-8'
    if (charset !== 'utf-8') {
      //June[debug]
      console.log('invalid charset');
    .....//省略
    }
      //June[debug]
     console.log('urlencoded, go to read');
    // read
    read(req, res, next, parse, debug, {
    .....//省略
    })
  }
}

read.js

function read(req, res, next, parse, debug, options) {
      //June[debug]
      console.log('req:'+req);
      .....//省略
 }

index.js(http-proxy-middleware)

function middleware(req, res, next) {
        if (shouldProxy(config.context, req)) {
            console.log("shouldProxy");//June
        .....//省略 

}

1.保持出问题的服务器端代码:
输出结果:
图片描述
分析:
1-10:可以看出app.use是按顺序向下执行的,此时启动了本地服务器。
接着发起了localhost:3000/check_f请求
11-13:走到json.js中间件进行验证,由于表单默认的content-type是application/x-www-form-urlencoded,所以json.js走到验证请求体解析时直接next跳出了,参考if(!shouldParse(req))部分代码。
14-19:继续向下走到urlencoded.js中间件进行验证,由于content-type是application/x-www-form-urlencoded,所以shouldParse(req)部分会继续read下去,对请求体进行解析。
20:回到index.js(http-proxy-middleware)进行代理转发
接着就遇到timeout 504的服务器错误了。

原因分析:
1.app.use中间件是有序执行的。
2.代理应该就一个左手交右手的动作,不该对请求体进行解析。
3.$.post默认提交的content-type是application/x-www-form-urlencoded格式的,这也就是为什么app.use(bodyParser.json())就算放前面不注释也不干扰的原因。

解决方式:
1.app.use(bodyParser.urlencoded())仍然放代理前面,不过注释掉-->不推荐,因为在项目中,不同模块不同功能,但共享一个app.js的入口文件,删掉此功能会对其它非跨域的表单请求产生影响。
2.请求体解析部分放在代理后面

修改后服务器端:

/**
 * proxy代理
 */
var proxy = require('http-proxy-middleware');//引入代理中间件
var dataProxy = proxy('/data', { target: "http://www.imooc.com/", changeOrigin: true });//将服务器代理到http://www.imooc.com上,本地服务器为localhost:3000
app.use('/data/*', dataProxy);//data子目录下的都是用代理
console.log("dataProxy->next");
/**
 * 解析请求的消息体
 */
var bodyParser = require('body-parser');
app.use(bodyParser.json());//返回一个只解析json的中间件,最后保存的数据都放在req.body对象上
app.use(bodyParser.urlencoded({ extended: true }));//返回的对象为任意类型

测试结果:
图片描述
分析:
1-10:可以看出app.use是按顺序向下执行的,此时启动了本地服务器。
接着发起了localhost:3000/check_f请求
完成代理转发
11-12:走到json.js/urlencoded.js中间件进行验证,实际上不做处理了。
13-15:正常的验证返回

总结:
实际上就是上面的3点原因分析。

另外:
这样改是不会对本地非跨域的请求产生影响的,自己写个本地的post响应去验证就可以,懒得写的可以参考post_l相关的。
代码参考: https://github.com/yifon/WebL... 中的check_f相关的,其它的都是其它一些不相关的功能。

阅读 6k
2 个回答

不对的地方欢迎指出

解决方式
1.app.use(bodyParser.urlencoded())仍然放代理前面
2.请求体解析部分放在代理后面
你上面的这个解决方式的描述和最终代码不同,代码最后是bodyparser的使用都放到代理中间件的后面

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏