29java socket模拟http【重点】springboot json post bug,wireshark抓http包

http协议长连接的粘拆包(短链接没有处理沾包的问题,但是会有何时并包的问题)【重点】 的实践

spring boot使用24netty(二十)http代理服务器【重点】的例子

netty server 使用netty(五)http服务的,添加一个代码:

ChannelFuture channelFuture = ctx.writeAndFlush(message);
     //   channelFuture.addListener(ChannelFutureListener.CLOSE);
        channelFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {

                /**
                 * 测试socket client,免去它处理http协议毡包拆包的义务
                 * https://www.cnblogs.com/silyvin/articles/12728538.html
                 */
                future.channel().close();

                if(!future.isSuccess()) {
                    future.cause().printStackTrace();
                    future.channel().close();
                }
            }
        });

 

为什么要睡5s秒,为了规避nagle算法(200ms超时强制发送),避免实际中,2个包在客户端结合成一个包发送,扰乱我们对服务端行为的判断

1 netty server

  length=body length>body length<body 无length
有fin

正常响应

阻塞直到fin

netty放弃响应

只读到length

部分或全部抛弃

缺省取0

然后同左

无fin 正常响应 一直阻塞  同上  同上
 无\r\n\r\n 同右 一直阻塞  同左  同左

 

 2 spring boot get

 

  length=0 length>body 无length length<body
有fin

无body正常响应

有body同最右

正常响应

无body同length=0

有body同右

 先正常响应

多于length的部分组成另一个http request再400

无fin

无body正常响应

有body同最右

正常响应

 

无body同length=0

有body同右

 同上
 无\r\n\r\n 同右

阻塞若干分钟后

放弃响应

 同左  同左

 

3 spring boot post

 

  length=body length>body length<body 无length
有fin

正常响应

无可解析body:阻塞直到fin

I/O error while reading input message

有可解析body:正常响应

400

JSON parse error

无论有无body:400

Required request body is missing

无fin

正常响应

无可解析body:阻塞若干分钟后

I/O error while reading input message

有可解析body:正常响应

 同上  同上
 无\r\n\r\n

同右

阻塞若干分钟后

放弃响应

 同左  同左

 *猜测:length<body情况,post剩余的报文被丢弃,get没有,get认定没有body,故把body认定为第二个请求,反序列化时认定为不合法的http header

 

tips

1 无论是netty还是springboot,长连接还是短链接,content-length都是必要的

netty有fin无content-length情况,netty无视fin前的body字节数组,认为没有body

springboot post有fin,无length情况,spring boot无视body字节数组,仍然报错Required request body is missing

原来,无论是浏览器,curl,httpclient,ajaxjs,都会自动加上length,而我们走原生socket则要手动加

 

2 content-length 2个坑

/**
* 坑1
* 由于没有\r\n\r\n,程序一直阻塞
*/
// sb.append("Content-Length:" + body.getBytes().length ); 忘记\r\n

/**
* 坑2 通过wireshark抓包与curl抓包对比发现,该参数起重要作用
* 有,server端阻塞等待直到\r\n\r\n + body.length后响应
* 无,server端阻塞等待知道\r\n\r\n结束,body部分或全部抛弃但不报错
*/

 3 http协议直到读到content-length个字节或-1较前者,触发response

netty server fin先于length个字节被读到,netty放弃响应

spring boot post 请求 fin先于length个字节被读到,或无fin的情况也有

无可解析body:spring boot报错. I/O error while reading input message

有可解析body:可正常响应

 我认为这是springboot不规范的地方,netty,无论有无body,必读到length个字节为止

 

4 那么body不止一段呢?

4.0 body被拆为2段分为2个包过去了,springboot不等第2个包,无视length,直接解析第1个包成功,导致报文误读;当然,本例不存在这样的情况,即使分为2个包:

private static final String body1 = "{\"pa\":\"BeJson\"";
private static final String body2 = ",\"pb\":\"sss\"}";
outputStream.write((body1).getBytes());
Thread.sleep(5000);
outputStream.write((body2).getBytes());

也会由于body1 springboot无法解析而阻塞等待第2个包,经实践证明了

猜测有可能spring boot在application/json情况下,有一个无视length认定是否合法body的过程

 

4.1 以结尾字符认定?

这样至少会每读到一个包都进行一次string反序列化,以识别尾部字符是否是合法json尾部

4.1.1 }

outputStream.write((body1 + "}").getBytes());
Thread.sleep(5000);
outputStream.write((body2).getBytes());

 提前响应,无视body2

 

4.1.2 ]

outputStream.write((body1 + "]").getBytes());
Thread.sleep(5000);
outputStream.write((body2).getBytes());

JSON parse error: Unexpected close marker ']': expected '}'

 

4.1.3

outputStream.write(("{\"name\":\"BeJson\",\"links\":[{\"name\":\"Google\"}").getBytes());
Thread.sleep(5000);
outputStream.write((",{\"name\":\"Baidu\"}]}").getBytes());

body1首尾{}组成一个合法的json首尾

然而springboot阻塞了5s,并没有拿到body1就认定为合法body就响应,说明springboot并不认同首尾合法的body1就真是合法的json,很有可能经历了一次json反序列化

 

4.2

从4.0 、4.1.1 、4.1.2 、4.1.3 共4个测试看来,有可能spirngboot是先判断首位字符合法性

若首尾字符合法(4.1.1 4.1.3)则进行json反序列化,反序列化成功(4.1)则直接响应;若反序列化失败(4.1.3)则阻塞等待后续

若首尾字符不合法则阻塞(4.0)等待后续、或直接报错(4.1.2)

 

4.3 小结

虽然springboot在无视length的情况下完成了报文的正常反序列化,但是可以肯定,会有本可避免的string反序列化、及json反序列化性能损耗

因为它是用报文当前可否成功反序列化(分2步 4.2)来决定是否开始响应,而不是等到length个字节再开始,前者会有多次反序列化(string & json)试错过程,后者只会有一次,比如

pack1-》springboot 反序列化失败,继续read阻塞

pack2-》springboot反序列化成功,开始response,不等你length

出现了2次认定过程,多了2次不必要的string反序列化,有时会出现一次json反序列化失败+一次json成功,耗费性能

 

 

5 如果是urlencode及其他二进制content-type呢?

经实践,当spring boot使用urlencode时,会遵循length字段,阻塞直到读到length个字节,不会出现3中,length>body直接正常响应这种事情

 

 

6 spring boot要求http request有host,netty(二十一)http代理服务器(二)简书之中实践下来,也要求有host

posted on 2020-04-18 22:13  silyvin  阅读(384)  评论(0编辑  收藏  举报