HHVM丢失http header的BUG
来源:互联网 发布:linux设置终端输出 编辑:程序博客网 时间:2024/06/05 23:01
本文为原创,转载请注明:http://blog.csdn.net/gistao
背景
分享一个hhvm使用http server方式来处理请求的问题及对应的patch。hhvm3+版本支持fastcgi模式,而之前的版本都只能用http serve模式来响应请求,由于hhvm的http server支持的功能比较弱,现在大部分使用场景都选择fastcgi了,而这次要说的问题仅存在于使用了http server模式的所有版本hhvm。
HHVM问题
我们发现线上的hhvm会有丢失http header的极小概率问题,在单台日请求量近千万情况下只有2个左右的发生概率,并且将出问题的url在线下复现,也没有复现,瞬间没有一点点线索了。没有办法,用笨办法:看代码+排除法,没有办法的办法。但事实证明往往笨办法也是最终的办法。
问题相关
简单的说,我们的服务架构是nginx作为反向代理服务器,来请求hhvm。Facebook的工程师基于libevent1.4.14b,增加了设置backlog功能和修复了一些内存泄漏问题,http的header的解析工作还是在libevent里独立完成的,截取http.c部分代码如下
enum message_read_statusevhttp_parse_headers(struct evhttp_request *req, struct evbuffer* buffer) { char *line; enum message_read_status status = MORE_DATA_EXPECTED; struct evkeyvalq* headers = req->input_headers; while ((line = evbuffer_readline(buffer)) != NULL) { char *skey, *svalue; if (*line == '\0') { /* Last header - Done */ status = ALL_DATA_READ; free(line); break; } /* Check if this is a continuation line */ if (*line == ' ' || *line == '\t') { if (evhttp_append_to_last_header(headers, line) == -1) goto error; free(line); continue; } /* Processing of header lines */ svalue = line; skey = strsep(&svalue, ":"); if (svalue == NULL) goto error; svalue += strspn(svalue, " "); if (evhttp_add_header(headers, skey, svalue) == -1) goto error; free(line); } return (status); error: free(line); return (DATA_CORRUPTED);}
能看出问题吗,由于定位问题过程比较曲折,这里不罗嗦了。
Libevent问题
bug出现在buffer.c里的readline函数,如下
char *evbuffer_readline(struct evbuffer *buffer){ u_char *data = EVBUFFER_DATA(buffer); size_t len = EVBUFFER_LENGTH(buffer); char *line; unsigned int i; for (i = 0; i < len; i++) { if (data[i] == '\r' || data[i] == '\n') break; } if (i == len) return (NULL); if ((line = malloc(i + 1)) == NULL) { fprintf(stderr, "%s: out of memory\n", __func__); return (NULL); } memcpy(line, data, i); line[i] = '\0'; /* * Some protocols terminate a line with '\r\n', so check for * that, too. */ if ( i < len - 1 ) { char fch = data[i], sch = data[i+1]; /* Drain one more character if needed */ if ( (sch == '\r' || sch == '\n') && sch != fch ) i += 1; } evbuffer_drain(buffer, i + 1); return (line);}
此函数的功能就是截取一行出来,然后循环解析完所有header。rfc2616规定行分隔符是\r\n,而libevent认定的换行符(eof)却是非常的宽松,比如\r就行。如果收包时恰好将\r和\n分离在两个包里,那么后边这个包解析时第一个字节就是\n,libevent解析时会认为这是一行(其实还是之前的行),而这行并没有内容,即内容是\0,这会造成evhttp_parse_headers函数直接认为header已经全部解析完毕了,也就是说丢失了header。
多说一句libevent的2x版本,相比之前的版本代码变化很大,单就解析header来说,已经提供了三种级别的换行符认定标准,不过默认还是最松散级别。
Libevent patch
由于我们这里的环境都是可控的,不会存在那些乱七八糟换行符,所以就按照强约束\r\n(LRCF)来解析,针对libevent1.4.14b的patch如下
--- ./buffer.c 2010-06-20 21:06:04.000000000 +0800+++ ./buffer.c 2014-04-21 14:22:25.783883798 +0800@@ -211,45 +211,35 @@ char * evbuffer_readline(struct evbuffer *buffer) {- u_char *data = EVBUFFER_DATA(buffer);- size_t len = EVBUFFER_LENGTH(buffer);- char *line;- unsigned int i;-- for (i = 0; i < len; i++) {- if (data[i] == '\r' || data[i] == '\n')- break;- }-- if (i == len)- return (NULL);-- if ((line = malloc(i + 1)) == NULL) {- fprintf(stderr, "%s: out of memory\n", __func__);- return (NULL);- }+ u_char *data = EVBUFFER_DATA(buffer);+ size_t len = EVBUFFER_LENGTH(buffer);+ char *line;+ unsigned int i;+ + for (i = 0; i < len; i++) {+ if (data[i] == '\r') {+ if (i + 1 < len && data[i+1] == '\n') {+ break;+ }+ }+ }++ if (i == len)+ return (NULL);++ if ((line = malloc(i + 1)) == NULL) {+ fprintf(stderr, "%s: out of memory\n", __func__);+ return (NULL);+ }- memcpy(line, data, i);- line[i] = '\0';+ memcpy(line, data, i);+ line[i] = '\0';- /*- * Some protocols terminate a line with '\r\n', so check for- * that, too.- */- if ( i < len - 1 ) {- char fch = data[i], sch = data[i+1];+ evbuffer_drain(buffer, i + 2);- /* Drain one more character if needed */- if ( (sch == '\r' || sch == '\n') && sch != fch )- i += 1;- }-- evbuffer_drain(buffer, i + 1);-- return (line);+ return (line); }- char * evbuffer_readln(struct evbuffer *buffer, size_t *n_read_out, enum evbuffer_eol_style eol_style)
- HHVM丢失http header的BUG
- HTTP的Header信息
- HTTP的Header
- http的header
- http 的header
- 非标准的http header
- 非标准的http header
- http-header的理解
- 【HHVM】HHVM中添加扩展的方法
- HHVM
- HHVM
- HHVM
- http协议header的内容
- HTTP的Header信息详解
- HTTP协议的header【网摘】
- 常用的http请求header
- http请求的header body
- HTTP Request的Header信息
- 安卓快速入门系列2(sharedPreferences的应用)
- Unity3d实现的十字路口的模拟(二)
- android中进行https连接的方式
- SQL优化(SQL TUNING)可大幅提升性能的实战技巧之一——让计划沿着索引跑
- Eclipse—为Android工程android-support-v4.jar包关联源代码的方法
- HHVM丢失http header的BUG
- ClipboardManager android剪切板使用
- java实现批量修改指定文件夹下所有后缀名的文件为另外后缀名的代码
- 欢迎使用CSDN-markdown编辑器
- mysql日志详细解析
- spring学习-分步getBean方法(1)
- Linux下./configure参数详解
- sqoop简单使用
- Perl脚本示例程序