参考文章:https://www.cnblogs.com/arnoldlu/p/6497837.html
通过HTTP 发出GET请求从服务器下载文件,特别是Bin文件等二进制文件。在TCP接收回调函数中,利用os_printf("%s",pdata)试图从串口输出GET的内容,但由于在二进制文件中可能存在\0,而导致输出中止,os_printf() 用于输出字符串,而字符串遇到'\0'结束。因此采用os_printf()以不合适,采用uart0_tx_buffer 输出指定数据缓存中指定长度的数据。
对于本文的主题,参考文章提到的http-parser和fast-http是解析HTTP协议两个不错的版本。本文以http-parser为研究对象。
对于发送GET请求request和解析 Response HTTP头,了解请求和响应时HTTP的组成是第一步。
HTTP请求和响应格式
Request格式——请求头(向服务器发出请求用)
<Request-line> METHOD /path-to-resource HTTP/Version-number
<headers> Header-Name-1:value
Header-Name-2:value
<blank line> 空行
[<request-body>] 可选的其他任意数据
说明:第一行必须是一个请求行(request-line),用来说明请求类型,要访问的资源以及所用到的HTTP版本;
第一行中Method 是请求方法-常用的有GET、POST。对于HTTP1.1目前支持7钟请求方法:GET/POST/HEAD/OPTION/PUT/DELETE/TRACE。他们的不同之处,点击此处
紧接的是一个header 小节, 用来说明服务器要使用的附加信息
之后,必须是一个空行
最后,是可选的其他任意数据---称为主体(body)
例子:
GET/sample.jsp HTTP/1.1 第一部分说明这是一个GET请求, 第二步峰说明资源在根目录的/sample.jsp ,版本为HTTP1.1
Accept:image/gif.image/jpeg.*/* 请求的第一个首部Header, 浏览器支持的EMIME类型分别是。。。
Accept-Language:zh-cn 浏览器支持的语言分别是:简体中文
Connection:Keep-Alive 该Header通常设置为Keep-Alive 表示客户端和服务器端是持久连接
Host:www.google.cn Host 这一Header指出请求的目的地是:www.google.cn
User-Agent:Mozila/4.0(compatible:MSIE5.01:Windows NT5.0)
Accept-Encoding:gzip,deflate. 浏览器支持的压缩编码是给IP和deflate
User-Agent这一header指出服务器端和客户端脚本均访问她,她是浏览器类型检测逻辑的重要基础,该信息由你的浏览器来定义,并且在每个请求中主动发送; 这是使用的额代理是Mozilla/4.0(域名)
第三部分是空行,即使不存在请求主题,这个空行也是必需的。
这个空行,非常重要,她表示请求头已经结束,接下去的是请求正文(body)。请求正文中可以包含客户提交的查询字符串信息。
例如username=fangle&password=1234
上面是我们发出请求的基础,在ESP8266中发出请求一定不能出错,这一步一出错,后面就OVER了。下面进行Respost头的分析,这是我们解析Recv的应答头的基础。
HTTP 响应格式
<status-line> Http/version-number status code message
<headers> header-Name-1:value
<blank line> 也有
[<respones-body> ] Optional Response body
第一行是状态行,由HTTP协议版本号,状态码、状态消息三部分组成,各部分以空格隔开。
HTTP/version-number表示HTTP协议的版本号, status-code 是数字形式的状态码 mesage是相应的状态描述。
HTTP/1.1中定义了5类状态码,状态码由三位数字组成,第一个数字表示响应的类别。
1XX 提示信息 --表示请求已被成功接收,继续处理
2XX 成功-表示请求已被成功接收,理解,接收
3XX 重定向- 请求有语法错误或请求无法实现
4XX 客户端错误 - 请求有语法错误或请求无法实现
5XX 服务器端错误 - 服务器未能实现合法的请求
200 OK:最常见的就是成功响应状态码200了, 这表明该请求被成功地完成,所请求的资源发送回客户端。
404 Not Found :请求资源不存在,例如输出URL错误。
对于重定向而言,就是新的URL会在response 中的Location中返回,浏览器将会自动使用新的URL发出新的Request。
第二行开始是Response的Header部分
其中对于我们解析下载的文件解析有用的Header为Content-Length: Content-Length 实体包头域用来指明正文body的长度,以字节方式存储的十进制数字来表示,也就是一个数字符号占一个字节,用于对应的ASCII码存储传输。
Content-Type实体报头域用来指明发送给接收者的实体正文的媒体类型。如Content-Type:text/html;charset=ISO-8859-1
缺图
其他Header部分以后补充
在Header与正文(Body)之间也有一个空行,这也是必需的。
这样根据这一特点,有人也用在其中找到"\r\n\r\n"来正文部分。这也是可行一个法子。
接下去,我们就来探究一下http-parser来解析Http的内容。 HTTP-Parser 是用C语言写的HTTP信息解析器。它可用于解析请求和响应。该解析器设计增强HTTP应用的性能。
再接下去,讲解下HTTP-Praser的使用。在每个TCP连接中使用一个http_parser对象,用http_parser_init()初始化她以及设置回调。
当socket(套接字)接收到Response时,执行解析器并检查错误。
http_parser_execute(parser, &settings, buf, recved); 执行解析过程,其原型
/**********
*执行解析器,返回解析的字节,Sets parse->http_errno
*input parse指向解析器对象; settings指向 回调函数结构体设置,data指向待解析的数据, len:带解析的长度
*****************/
size_t http_parser_execute(http_parser *parser,
const http_parser_settings *settings,const char *data,
size_t len);
对于结构体http_parser_settings
struct http_parser_settings {
http_cb on_message_begin;
http_data_cb on_url;
http_data_cb on_status;
http_data_cb on_header_field;
http_data_cb on_header_value;
http_cb on_headers_complete;
http_data_cb on_body;
http_cb on_message_complete;
/* When on_chunk_header is called, the current chunk length is stored
* in parser->content_length.
*/
http_cb on_chunk_header;
http_cb on_chunk_complete;
}; http_cb 和 http_data_cb都是函数指针类型,如下所示
typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
typedef int (*http_cb) (http_parser*);
代码一般如下所示:
int on_header_field(http_parser* _, const char* at, size_t length) {
(void)_;
printf("Header field: %.*s\n", (int)length, at);
return 0;
}
int on_header_value(http_parser* _, const char* at, size_t length) {
(void)_;
printf("Header value: %.*s\n", (int)length, at);
return 0;
}
int on_body(http_parser* _, const char* at, size_t length) {
(void)_;
printf("Body: %.*s\n", (int)length, at); //at就是所有的正文数据,以及lenght是其长度
return 0;
}
static http_parser_settings settings_null = //http_parser的回调函数,需要获取HEADER或者BODY信息,可以在这里面处理。
{.on_message_begin = 0
,.on_header_field = on_header_field
,.on_header_value = on_header_value
,.on_url = 0
,.on_status = 0
,.on_body = on_body
,.on_headers_complete = 0
,.on_message_complete = 0
};
在ESP8266 的接收回调函数中:
#include "http_parser.h"
static http_parser 8parser;
void espconn_recv_cb( void *arg, char *pdata, unsigned short len)
{
struct espconn *pesp_conn = arg;
parase_recv(pdata, len);
}
void parase_recv(const char *buf, unsigned short len)
{
int i;
// float start, end;
size_t parsed; //解析的字节数
parser = (struct http_parser *)os_zalloc(http_parser);
http_parser_init(parse,HTTP_RESPONSE); //初始化Parser 为Response类型
parsed = http_parser_execute(parser, &settings_null, buf, len); 执行解析过程
free(parser)
}
最后我们在讲讲那种偷懒的方法——找"\r\n\r\n"。
#include <stdlib>
int length=0;
char *binContent= NULL;
void parase_recv(const char *buf, unsigned short len)
{
char *PA;
char *PB;
char *PC;
char strLength[32]={0};
if(!(*buf)) return;
PA = buf;
PB = os_strstr(PA,"Content-Length:")
PC = os_strstr(PB,"\r\n");
memcpy(strLength,PB, PC-PB);
length = atoi(strLength);
binContent = (char *)zalloc(length );
PC = NULL;
if(PC= os_strstr(PB,"\r\n\r\n"))
PC +=os_strlen("\r\n\r\n");
memcpy(binConten, PC, length);
}
对于http-paraser的代码解析,具体再下一章具体解释。