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(); } } });
0
为什么要睡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