美文网首页
通过node的pipe实现请求代理

通过node的pipe实现请求代理

作者: 墙角儿的花 | 来源:发表于2017-03-03 08:34 被阅读0次

    引言

    充分利用node的高吞吐量和java服务的稳定性,特搭建node+java的开发和运行框架。前端代码采用js,一部分作为界面代码运行于浏览器,一部分做为转发代理运行于node。后端java采用spring实现restful的web service。
    从浏览器访问restful service,需要经过node转发,转发请求的代码采用node的pipe,该方法实现流间的管道对接,减少了传统的从一个流中读取内容然后写入目标流中的系统开销。
    在实际的实现中遇见一个问题:浏览器发送post请求,经过node代理将请求转发给tomcat下的spring restful api,而spring无法获取到请求体。

    问题描述

    浏览器通过jquery.ajax发送post请求,请求一个restful api,如:/sh/person,请求体为{"name":"test","age":66},
    jquery发送post请求:

    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请求转发到后端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中解析请求体,报如下错误,该错最终是由tomcat读取请求的编码信息时抛出的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转发,直接发送post请求是没有问题的,如通过postman工具直接发送post请求是没有问题的,且node的转发实现不去pipe请求流,调整为直接创建新的请求并重新发送请求体也是没有问题的

    2. 跟踪spring源码发现,发生问题的测试案例下,查看request的输入流inputstream.available()为0,运行read同样抛出以上异常。也就是说经过node转发过来的流body体为空。

    3. 同样的方式将请求转发到一个jetty server里,读取输入流也报错

      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)
      

    结论

    通过以上排查确定后端问题的可能性非常小,tomcat和jetty读取body都会出错。
    那么问题还是在node端。
    从后端调试发现node转发post过来的body体为空,说明应该是有动作已经读取了请求的输入流,因为请求的输入流只能读取一次。
    最后,发现node端有如下代码:

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

    该代码是由express-generator自动生成,因此一直没得到关注。body-parser用于body体的解析,因此其自动读取了请求输入流导致浏览器请求被pipe到后端后,后端读取的body体为空。
    该代码用不上,删除掉即可。

    相关文章

      网友评论

          本文标题:通过node的pipe实现请求代理

          本文链接:https://www.haomeiwen.com/subject/dsdlgttx.html