最近需要新增一个 REST API 的功能,然后使用到常用的开源库 civetweb 来实现这个功能
测试HTTP接口时,发现 postman 响应报错 **
Error: Parse Error: Expected HTTP/
** 01 | 问题描述
使用 postman 测试使用开源库 civetweb 实现的 HTTP 接口时,postman 报错 Error: Parse Error: Expected HTTP/
02 | 问题追踪
错误信息 Error: Parse Error: Expected HTTP/
有一个关键字:Parse
,简要的说明了响应报文中存在解析校验错误的模块,那么现在问题就变成了找到报文中那里出错导致解析/校验失败
网络相关问题解决三步骤:
- 检查自己响应报文处代码
void send_Http_cmd_error_rsp(struct mg_connection *conn)
{
if (NULL == conn)
{
return;
}
Json::Value root;
root["errorCode"] = 400;
root["errorMsg"] = "Bad Request";
string resBody = root.toStyledString();
int resLen = resBody.length();
printf("resLen = [%d], resBody = %s\n", resLen, resBody.c_str());
int ret = mg_printf(conn, "%s",
"HTTP/1.1 400 Bad Request\r\n"
"Cache-Control: no-cache\r\n"
"Content-Type: text/html;charset=utf-8\r\n");
printf("ret = [%d]", ret);
ret = mg_printf(conn, "Connection: %s\r\n", "keep-alive");
printf("ret = [%d]", ret);
ret = mg_printf(conn, "Content-Length: %d\r\n\r\n", resLen);
printf("ret = [%d]", ret);
ret = mg_printf(conn, "%s", resBody.c_str());
printf("ret = [%d]", ret);
}
检查中并没有发现函数用错、变量赋值错误、传值错误等问题,而且在每一次调用mg_printf()
的时候我都把返回值打印了,并没有执行出错的情况,那么基本可以排除了代码逻辑问题
- Wireshark 抓包分析
这里通过 Wireshark 捕抓 ARM 和 PC 之间交互时的网络数据包
从上面抓包获取到的图可以看到,交互时产生的数据包确实存在问题,HTTP对应的数据包尾缀Malformed Packet
代表的意思是畸形包(在这里畸形包就是不符合各层协议规范,无法被Wireshark解析的错误数据包),这里就开始找到异常现象了
通过进一步的追踪这个HTTP数据包,发现HTTP流层数据是我所发送的数据无误,但是TCP流层却多出了其他数据
-
那么多出这坨东西,为什么会导致解析失败呢?
回头看了下代码里我指定的
Context-Length
字段长度是我发送数据的长度,那么现在多出来的数据就导致了实际长度比我预设的长度要大,所以这个数据包校验不通过,认为是不合法,是个畸形包 -
为什么我没有发送这坨东西,但是它却真真实实的出现在了数据包里?
因为一开始已经确定了自己并没有发送这坨多出来的数据,而且这坨数据显得十分的官方化,所以基本上确定这是源码本身自己处理发送的数据,所以问题最后就是找到这个多出来的数据是在源码哪个位置进行处理发送的
- 开源库源码解析
众所周知 HTTP 状态码 404 对应的错误消息就是 Not Found
因为这个错误出现的位置是在回复响应报文阶段,所以在查看源码解析的时候,可以大大的缩小范围,直接找 request_handler
的文件进行搜索mg_printf()
Not Found | Not found
关键词来确定问题代码处。
在 civetweb.c
文件搜索 Not Found
和 mg_printf()
找到了内置的发送错误消息的函数mg_send_http_error()
和 mg_send_http_error_impl()
int
mg_send_http_error(struct mg_connection *conn, int status, const char *fmt, ...)
{
va_list ap;
int ret;
va_start(ap, fmt);
ret = mg_send_http_error_impl(conn, status, fmt, ap);
va_end(ap);
return ret;
}
在mg_send_http_error_impl()
函数最后找到了这个消息发送的代码
/* HTTP responses 1xx, 204 and 304 MUST NOT send a body */
if (has_body) {
/* For other errors, send a generic error message. */
const char *status_text = mg_get_response_code_text(conn, status);
mg_printf(conn, "Error %d: %s\n", status, status_text);
mg_write(conn, errmsg_buf, strlen(errmsg_buf));
} else {
/* No body allowed. Close the connection. */
DEBUG_TRACE("Error %i", status);
}
解析这个函数了解到,Error 404: Not Found 这个消息是通过传进来的状态码 404,调用mg_get_response_code_txt()
获取到的,而 Not found 这个消息是通过外部传进来的,那么就可以查找调用最外层函数mg_send_http_error(conn, 404, "%s", "Not found)
的地方所以确定了这多出来的消息是调用这个函数发送的,那么在它的最外层mg_send_http_error()
函数进行查找在哪里设置了 404 Not found
在找到的三处地方,通过把 Not found 修改成其他消息,重新走一下分析过程,最终确定了调用位置是handle_request()
函数处
/* 11. File does not exist, or it was configured that it should be
* hidden */
if (!is_found || (must_hide_file(conn, path))) {
mg_send_http_error(conn, 404, "%s", "fuck fuck2");
return;
}
我对上面这段代码的理解大概是
如果响应消息中需要的配置信息不存在或者需要被隐藏的情况下,会触发自动发送 404 Not found 的动作
03 | 问题原因
综上所述:
-
问题表面上是效应报文中实际数据长度大于设定的
Context-Length
值,导致数据包错误 -
问题实际上是在执行我设定的
mg_printf()
之前,civetweb 本身就已经在handle_request()
处做了响应处理,这时候自然识别到的报文是没有任何响应头字段的,所以触发了上面自动发送 404 Not Found 的动作
04 | 问题解决
-
如果仅针对表面现象
可以直接修改响应头中的 Content-Length 值,使其足够大即可,实验证明这个方法确实可以(网上其他碰到类似问题的解决方法也都采用的是这个),但是如果下次触发的是其他奇奇怪怪的动作,这个值还是不够大呢?
所以还是得从根源出发,解决问题
-
根源解决
既然我调用
mg_printf()
处理完成前, civetweb 本身的handle_request()
就已经开始运作了,那么只要确保*等我执行完mg_printf()
之后再让handle_request()
进行运作就好处理方式就是在代码的最后加上一定的延时,确保我的消息已经完成了即可,我这里设置了10秒的延时
void send_Http_cmd_error_rsp(struct mg_connection *conn) { if (NULL == conn) { return; } Json::Value root; root["errorCode"] = 400; root["errorMsg"] = "Bad Request"; string resBody = root.toStyledString(); int resLen = resBody.length(); printf("resLen = [%d], resBody = %s\n", resLen, resBody.c_str()); int ret = mg_printf(conn, "%s", "HTTP/1.1 400 Bad Request\r\n" "Cache-Control: no-cache\r\n" "Content-Type: text/html;charset=utf-8\r\n"); printf("ret = [%d]", ret); ret = mg_printf(conn, "Connection: %s\r\n", "keep-alive"); printf("ret = [%d]", ret); ret = mg_printf(conn, "Content-Length: %d\r\n\r\n", resLen); priintf("ret = [%d]", ret); ret = mg_printf(conn, "%s", resBody.c_str()); printf("ret = [%d]", ret); sleep(10); }
05 | 总结
好好干,总得练习两年半