昨天我們使用的網關轉發數據時出了點問題!
情景是這樣的,另一方以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); });
