http chunked包记录
作者:
| 更新日期:http协议包比较简单, 最近遇到chunked包的一个问题, 记录一下
本文首发于公众号:天空的代码世界,微信号:tiankonguse
背景
http协议是一种文本协议, 文本协议有一个先天优势: 容易理解,容易阅读.
之前使用http协议的时候, 收到的包都有Conent-Length
, 所以判断包是否收完直接判断长度即可.
今天同事说自己http包解包后有问题, 于是帮忙看了一下, 发现是chunked包, tcpdump抓包之后发现包被分片了.
这里了解一下chunked包, 顺便记录下业务该怎么处理chunked包.
首先看一个知识点, 首先去RFC和维基百科看看.
看完实际上就可以不用看我这篇文章了, 那上面写的比我这详细多了.
但是为了自己后续快速回顾知识点, 这里还是记录一下.
安全性前提
一般http的包头很小, 所以很多人都会假设包头肯定在第一个tcp包头.
这样其实是不合理的,我们应该按照http的协议来找包头.
bool checkCompleteHttpHead(const char *buf,int len){
if(strnstr(buf, "\r\n\r\n", len) == NULL){
return 0;
}else{
return 1;
}
}
Content-Length
在有Content-Length
的时候, 我们只需要找到Content-Length
, 然后就可以计算出包长了.
由于Content-Length
类型的包是一个包体连续的包, 所以直接取自己的数据即可.
比如我们返回的数据都是json, 所以一般都是直接去json数据.当然标准的做法应该是分析包头,得到包头和包体.
当然上面的方法是错误的, 我们应该选分析包头, 是否正确的返回数据.
Transfer-Encoding
Transfer-Encoding
是chunked的包是http的另一种数据传输机制, 当网络传输的时候, 可能会进行分片, 每个片头会记录每个分片的大小.
格式
- 如果一个HTTP消息(请求消息或应答消息)的Transfer-Encoding消息头的值为chunked,那么,消息体由数量未定的块组成,并以最后一个大小为0的块为结束。
- 每一个非空的块都以该块包含数据的字节数(字节数以十六进制表示)开始,跟随一个CRLF (回车及换行),然后是数据本身,最后块CRLF结束。
- 最后一块是单行,由块大小(0),一些可选的填充白空格,以及CRLF。最后一块不再包含任何数据,但是可以发送可选的尾部,包括消息头字段。
- 消息最后以CRLF结尾。
样例
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
25
This is the data in the first chunk
1C
and this is the second one
3
con
8
sequence
0
###检查包完整性
根据上面的协议, 最后一个分片是0\r\n\r\n
, 所以我们可以检查是否有这个字符串来判断是都得到完整的包.
if(!strncmp(pEnd,"0\r\n\r\n",5)){
return len;
}else{
return 0;
}
应用
这种方式的包对于我们应用来说, 其实有点麻烦的.
收到一个http包后, 我们需要解析包, 而不能像Content-Length
那样直接find包体的前缀和后缀来得到自己想要的数据啦.
这时候我们需要处理chunked包, 去掉多余的分片标示的相关字符.
string strHttpRsp = "", strRspBody = "";
//填充http包到string, 回包是个json
strHttpRsp.assign(data, len);
string::size_type stPos = strHttpRsp.find_first_of('{');
if (stPos == string::npos) {
return -__LINE__;
}
string::size_type stEndPos;
//去掉包体中 "\r\n1500\r\n" 这样的字符
while (stPos < strHttpRsp.length()) {
stEndPos = strHttpRsp.find("\r\n", stPos);
if (stEndPos == string::npos || strHttpRsp.length() <= stEndPos + 2) {
//最后一行
strRspBody += strHttpRsp.substr(stPos);
break;
} else {
//找到下一个字节数前面的\r\n
strRspBody += strHttpRsp.substr(stPos, stEndPos - stPos);
//跳过字节数
stPos = strHttpRsp.find("\r\n", stEndPos + 2);
if (stPos != string::npos && stPos < strHttpRsp.length() - 2) {
//跳过字节数后面的\r\n
stPos += 2;
} else {
break;
}
}
}
stEndPos = strHttpRsp.find_last_of('}');
if (stEndPos == string::npos){
return -__LINE__;
}
strHttpResponse = strRspBody.substr(0, stEndPos + 1);
本文首发于公众号:天空的代码世界,微信号:tiankonguse
如果你想留言,可以在微信里面关注公众号进行留言。