通過node的pipe實(shí)現(xiàn)請(qǐng)求代理

引言

充分利用node的高吞吐量和java服務(wù)的穩(wěn)定性,特搭建node+java的開發(fā)和運(yùn)行框架。前端代碼采用js,一部分作為界面代碼運(yùn)行于瀏覽器,一部分做為轉(zhuǎn)發(fā)代理運(yùn)行于node。后端java采用spring實(shí)現(xiàn)restful的web service。
從瀏覽器訪問restful service,需要經(jīng)過node轉(zhuǎn)發(fā),轉(zhuǎn)發(fā)請(qǐng)求的代碼采用node的pipe,該方法實(shí)現(xiàn)流間的管道對(duì)接,減少了傳統(tǒng)的從一個(gè)流中讀取內(nèi)容然后寫入目標(biāo)流中的系統(tǒng)開銷。
在實(shí)際的實(shí)現(xiàn)中遇見一個(gè)問題:瀏覽器發(fā)送post請(qǐng)求,經(jīng)過node代理將請(qǐng)求轉(zhuǎn)發(fā)給tomcat下的spring restful api,而spring無法獲取到請(qǐng)求體。

問題描述

瀏覽器通過jquery.ajax發(fā)送post請(qǐng)求,請(qǐng)求一個(gè)restful api,如:/sh/person,請(qǐng)求體為{"name":"test","age":66},
jquery發(fā)送post請(qǐng)求:

var data = {name:"test",age:66};
            $.ajax({
                type:"post",
                url:"/sh/person",
                data:JSON.stringify(data),
                //processData:false,
                success:function(d){
                },
                dataType:'json',
                contentType:"application/json; charset=utf-8"
            });

由node7通過pipe將瀏覽器的api請(qǐng)求轉(zhuǎn)發(fā)到后端spring restful web service
代碼:

app.use('/sh/', function(req, res, next) {
  var options = {
    host: "127.0.0.1",
    port: "8080",
    path: '/sh'+req.url,
    method: req.method,
    headers: req.headers 
  };
  var request = http.request(options,function(response){
    res.statusCode = response.statusCode;
    response.pipe(res);
  }).on("error",function(){
    res.statusCode = 503;
    res.end();
  });
  req.pipe(request);
  // request.write(JSON.stringify({name:"test",age:66}));
  // request.end();
});

tomcat8下spring4 restful 控制器的方法

@RequestMapping(value = "/person", method = RequestMethod.POST)
    public @ResponseBody
    Person addPerson(@RequestBody Person person) {
        personService.add(person);
        return person;
    }

spring在DispatchServlet中解析請(qǐng)求體,報(bào)如下錯(cuò)誤,該錯(cuò)最終是由tomcat讀取請(qǐng)求的編碼信息時(shí)拋出的SocketTimeoutException引起:


org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: null; nested exception is java.net.SocketTimeoutException
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:228)
    ......
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.net.SocketTimeoutException
    at org.apache.tomcat.util.net.NioBlockingSelector.read(NioBlockingSelector.java:202)
    at org.apache.tomcat.util.net.NioSelectorPool.read(NioSelectorPool.java:250)
    at org.apache.tomcat.util.net.NioSelectorPool.read(NioSelectorPool.java:231)
    at org.apache.coyote.http11.InternalNioInputBuffer.fill(InternalNioInputBuffer.java:133)
    at org.apache.coyote.http11.InternalNioInputBuffer$SocketInputBuffer.doRead(InternalNioInputBuffer.java:177)
    at org.apache.coyote.http11.filters.IdentityInputFilter.doRead(IdentityInputFilter.java:110)
    at org.apache.coyote.http11.AbstractInputBuffer.doRead(AbstractInputBuffer.java:414)
    at org.apache.coyote.Request.doRead(Request.java:476)
    at org.apache.catalina.connector.InputBuffer.realReadBytes(InputBuffer.java:350)
    at org.apache.tomcat.util.buf.ByteChunk.substract(ByteChunk.java:395)
    at org.apache.catalina.connector.InputBuffer.read(InputBuffer.java:375)
    at org.apache.catalina.connector.CoyoteInputStream.read(CoyoteInputStream.java:190)
    at com.fasterxml.jackson.core.json.ByteSourceJsonBootstrapper.ensureLoaded(ByteSourceJsonBootstrapper.java:489)
    at 
    ......

排查

  1. 不通過node的pipe轉(zhuǎn)發(fā),直接發(fā)送post請(qǐng)求是沒有問題的,如通過postman工具直接發(fā)送post請(qǐng)求是沒有問題的,且node的轉(zhuǎn)發(fā)實(shí)現(xiàn)不去pipe請(qǐng)求流,調(diào)整為直接創(chuàng)建新的請(qǐng)求并重新發(fā)送請(qǐng)求體也是沒有問題的

  2. 跟蹤spring源碼發(fā)現(xiàn),發(fā)生問題的測試案例下,查看request的輸入流inputstream.available()為0,運(yùn)行read同樣拋出以上異常。也就是說經(jīng)過node轉(zhuǎn)發(fā)過來的流body體為空。

  3. 同樣的方式將請(qǐng)求轉(zhuǎn)發(fā)到一個(gè)jetty server里,讀取輸入流也報(bào)錯(cuò)

    org.eclipse.jetty.io.EofException: early EOF
    at org.eclipse.jetty.server.HttpInput.read(HttpInput.java:65)
    at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
    at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
    at java.io.InputStreamReader.read(InputStreamReader.java:184)
    

結(jié)論

通過以上排查確定后端問題的可能性非常小,tomcat和jetty讀取body都會(huì)出錯(cuò)。
那么問題還是在node端。
從后端調(diào)試發(fā)現(xiàn)node轉(zhuǎn)發(fā)post過來的body體為空,說明應(yīng)該是有動(dòng)作已經(jīng)讀取了請(qǐng)求的輸入流,因?yàn)檎?qǐng)求的輸入流只能讀取一次。
最后,發(fā)現(xiàn)node端有如下代碼:

var bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

該代碼是由express-generator自動(dòng)生成,因此一直沒得到關(guān)注。body-parser用于body體的解析,因此其自動(dòng)讀取了請(qǐng)求輸入流導(dǎo)致瀏覽器請(qǐng)求被pipe到后端后,后端讀取的body體為空。
該代碼用不上,刪除掉即可。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,537評(píng)論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,262評(píng)論 6 342
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,789評(píng)論 11 349
  • 有人在背后默默支持你的一切一切的感覺真是太好了 即便是摔倒100次 即便無法自顧溫飽 即便是別人眼中的無所事事 也...
    矢耶閱讀 134評(píng)論 0 0
  • 那時(shí)候,我們光著腳丫在操場唱歌,太陽把臉曬得紅彤彤的。有時(shí)候也會(huì)暢想未來,比如會(huì)去怎樣的大學(xué),會(huì)遇見怎樣的人。 ...
    北方囡囡閱讀 326評(píng)論 0 0

友情鏈接更多精彩內(nèi)容