0%

开源HTTP解析器---http-parser和fast-http

由于项目中遇到需要发送http请求,然后再解析接收到的响应。大概在网上搜索了一下,有两个比较不错,分别是http-parserfast-http
http-parser是由C编写的工具;fast-http是大部分移植自http-parser,用lisp语言编写的,不太适合目前的项目。fast-http介绍文档《fast-http》。
有一篇文章《HTTP 协议解析库:fast-http》介绍了fast-http,并且和http-parser进行了简单比较,结论是fast-http更快,快一倍。

另一个关于HTTP请求的简单介绍《为你详细解读HTTP请求头的具体含意》,可以作为背景知识简要阅读。
关于http-parser有一篇不错的实战文档《【slighttpd】基于lighttpd架构的Server项目实战(7)—http-parser》。

HTTP请求和响应格式

关于HTTP请求和响应的科普文档:《HTTP请求和响应格式》、《HTTP请求格式和http响应格式》、《#HTTP协议学习# (一)request 和response 解析》、《HTTP协议详解》。

Request格式

先看看请求纤细的结构,request消息分为3部分:Request line、Request header、Body。Request header和Body之间有个空行:

img

第一行中的Method表示请求方法,比如”POST”,”GET”, Path-to-resoure表示请求的资源, Http/version-number 表示HTTP协议的版本号。

当使用的是”GET” 方法的时候, body是为空的。

Request line

请求的第一行是“方法 URL 议 / 版本”: GET/sample.jsp HTTP/1.1
以上代码中“ GET ”代表请求方法,“ /sample.jsp ”表示 URI ,“ HTTP/1.1 代表协议和协议的版本。
根据 HTTP 标准, HTTP 请求可以使用多种请求方法。例如: HTTP1.1 目前支持 7 种请求方法: GET 、 POST 、 HEAD、 OPTIONS 、 PUT 、 DELETE 和 TARCE 。

方法
GET 请求获取由Request-URI所标识的资源。
POST Request-URI所标识的资源后附加新的数据。
HEAD 请求获取由Request-URI所标识的资源的响应消息报头。
PUT 请求服务器存储一个资源,并用Request-URI作为其标识。
DELETE 请求服务器删除由Request-URI所标识的资源。
TRACE 请求服务器回送收到的请求信息,主要用语测试或诊断。

在Internet应用中,最常用的方法是GET和POST。

URI完整地指定了要访问的网络资源,通常只要给出相对于服务器的根目录的相对目录即可,因此总是以“/”开头,最后,协议版本声明了通信过程中使用HTTP的版本。

Request header

请求头包含许多有关的客户端环境和请求正文的有用信息。例如,请求头可以声明浏览器所用的语言,请求正文的长度等。
Accept:image/gif.image/jpeg./
Accept-Language:zh-cn
Connection:Keep-Alive
Host:localhost
User-Agent:Mozila/4.0(compatible:MSIE5.01:Windows NT5.0)
Accept-Encoding:gzip,deflate.

Request body

请求头和请求正文之间是一个空行,这个行非常重要,它表示请求头已经结束,接下来的是请求正文。请求正文中可以包含客户提交的查询字符串信息:
username=jinqiao&password=1234
在以上的例子的HTTP请求中,请求的正文只有一行内容。当然,在实际应用中,HTTP请求正文可以包含更多的内容。

HTTP请求方法我这里只讨论GET方法与POST方法
l GET方法
GET方法是默认的HTTP请求方法,我们日常用GET方法来提交表单数据,然而用GET方法提交的表单数据只经过了简单的编码,同时它将作为URL的一部分向Web服务器发送,因此,如果使用GET方法来提交表单数据就存在着安全隐患上。

例如:Http://127.0.0.1/login.jsp?Name=zhangshi&Age=30&Submit=%cc%E+%BD%BB
从上面的URL请求中,很容易就可以辩认出表单提交的内容。(?之后的内容)另外由于GET方法提交的数据是作为URL请求的一部分所以提交的数据量不能太大
l POST方法
POST方法是GET方法的一个替代方法,它主要是向Web服务器提交表单数据,尤其是大批量的数据。POST方法克服了GET方法的一些缺点。通过POST方法提交表单数据时,数据不是作为URL请求的一部分而是作为标准数据传送给Web服务器,这就克服了GET方法中的信息无法保密和数据量太小的缺点。因此,出于安全的考虑以及对用户隐私的尊重,通常表单提交时采用POST方法。
  从编程的角度来讲,如果用户通过GET方法提交数据,则数据存放在QUERY_STRING环境变量中,而POST方法提交的数据则可以从标准输入流中获取。

Response格式

Response消息的结构和Request结构基本一样。同样也为三部分:Response Line、Response header、Body。Response header和Body之间也有个空行:

img

Response line

HTTP/version-number表示HTTP协议的版本号,status-code和message请看下面详细解释。
Response 消息中的第一行叫做状态行,由HTTP协议版本号, 状态码, 状态消息 三部分组成。
状态行由协议版本、数字形式的状态代码、及相应的状态描述,各元素之间以空格分隔。
状态码用来告诉HTTP客户端,HTTP服务器是否产生了预期的Response.
HTTP/1.1中定义了5类状态码, 状态码由三位数字组成,第一个数字定义了响应的类别

1XX 提示信息 - 表示请求已被成功接收,继续处理
2XX 成功 - 表示请求已被成功接收,理解,接受
3XX 重定向 - 要完成请求必须进行更进一步的处理
4XX 客户端错误 - 请求有语法错误或请求无法实现
5XX 服务器端错误 - 服务器未能实现合法的请求

200 OK:最常见的就是成功响应状态码200了, 这表明该请求被成功地完成,所请求的资源发送回客户端。

302 Found:重定向,新的URL会在response 中的Location中返回,浏览器将会自动使用新的URL发出新的Request。
304 Not Modified:代表上次的文档已经被缓存了, 还可以继续使用。
400 Bad Request: 客户端请求与语法错误,不能被服务器所理解。
401 Unauthonzed 请求未经授权。这个状态代码必须和WWW-Authenticate报头域一起使用
403 Forbidden: 服务器收到请求,但是拒绝提供服务。服务器通常会在响应正文中给出不提供服务的原因。
404 Not Found:请求的资源不存在,例如,输入了错误的URL。
500 Internal Server Error: 服务器发生不可预期的错误,导致无法完成客户端的请求。
503 Server Unavailable: 服务器当前不能够处理客户端的请求,在一段时间之后,服务器可能会恢复正常。

Response header

Location:Location响应报头域用于重定向接受者到一个新的位置。例如:客户端所请求的页面已不存在原先的位置,为了让客户端重定向到这个页面新的位置,服务器端可以发回Location响应报头后使用重定向语句,让客户端去访问新的域名所对应的服务器上的资源。当我们在JSP中使用重定向语句的时候,服务器端向客户端发回的响应报头中,就会有Location响应报头域。

Server:Server响应报头域包含了服务器用来处理请求的软件信息。它和User-Agent请求报头域是相对应的,前者发送服务器端软件的信息,后者发送客户端软件(浏览器)和操作系统的信息。下面是Server响应报头域的一个例子:Server: Apache-Coyote/1.1

WWW-Authenticate:WWW-Authenticate响应报头域必须被包含在401(未授权的)响应消息中,这个报头域和前面讲到的Authorization请求报头域是相关的,当客户端收到401响应消息,就要决定是否请求服务器对其进行验证。如果要求服务器对其进行验证,就可以发送一个包含了 Authorization报头域的请求,下面是WWW-Authenticate响应报头域的一个例子:WWW-Authenticate: Basic realm=”Basic Auth Test!”从这个响应报头域,可以知道服务器端对我们所请求的资源采用的是基本验证机制。

Content-Encoding:Content-Encoding实体报头域被使用作媒体类型的修饰符,它的值指示了已经被应用到实体正文的附加内容编码,因而要获得Content- Type报头域中所引用的媒体类型,必须采用相应的解码机制。Content-Encoding主要用语记录文档的压缩方法,下面是它的一个例子: Content-Encoding: gzip。如果一个实体正文采用了编码方式存储,在使用之前就必须进行解码。

Content-Language:Content-Language实体报头域描述了资源所用的自然语言。Content-Language允许用户遵照自身的首选语言来识别和区分实体。如果这个实体内容仅仅打算提供给丹麦的阅读者,那么可以按照如下的方式设置这个实体报头域:Content-Language: da。如果没有指定Content-Language报头域,那么实体内容将提供给所以语言的阅读者。

Content-Length: Content-Length实体报头域用于指明正文的长度,以字节方式存储的十进制数字来表示,也就是一个数字字符占一个字节,用其对应的ASCII码存储传输。
要注意的是:这个长度仅仅是表示实体正文的长度,没有包括实体报头的长度。

Content-Type实体报头域用语指明发送给接收者的实体正文的媒体类型。例如:Content-Type: text/html;charset=ISO-8859-1

PS:这里主要关注Content-Length和Content-Type两种类型,用于提取Body实体。

Last-Modified实体报头域用于指示资源最后的修改日期及时间。

Expires实体报头域给出响应过期的日期和时间。通常,代理服务器或浏览器会缓存一些页面。当用户再次访问这些页面时,直接从缓存中加载并显示给用户,这样缩短了响应的时间,减少服务器的负载。为了让代理服务器或浏览器在一段时间后更新页面,我们可以使用Expires实体报头域指定页面过期的时间。当用户又一次访问页面时,如果Expires报头域给出的日期和时间比Date普通报头域给出的日期和时间要早(或相同),那么代理服务器或浏览器就不会再使用缓存的页面而是从服务器上请求更新的页面。不过要注意,即使页面过期了,也并不意味着服务器上的原始资源在此时间之前或之后发生了改变。

http-parser

下载代码:

1
git clone <https://github.com/arnoldlu/http-parser.git>

编译安装

1
2
3
4
make
make parsertrace
make url_parser
sudo make install (会将lib安装到/usr/lib/libhttp_parser.so.2.7.1/usr/lib/libhttp_parser.so,在使用的时候编译中增加-lhttp_parser)

一个实例测试

1
2
3
make http_parser.o
gcc -Wall -Wextra -Werror -Wno-error=unused-but-set-variable -O3 http_parser.o demo.c -o demo
./demo

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#include "http_parser.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>

static http_parser *parser;

int on_message_begin(http_parser* _) {
(void)_;
printf("\n***MESSAGE BEGIN***\n\n");
return 0;
}

int on_headers_complete(http_parser* _) {
(void)_;
printf("\n***HEADERS COMPLETE***\n\n");
return 0;
}

int on_message_complete(http_parser* _) {
(void)_;
printf("\n***MESSAGE COMPLETE***\n\n");
return 0;
}

int on_url(http_parser* _, const char* at, size_t length) {
(void)_;
printf("Url: %.*s\n", (int)length, at);
return 0;
}

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);
return 0;
}

static http_parser_settings settings_null = http_parser的回调函数,需要获取HEADER后者BODY信息,可以在这里面处理。
{.on_message_begin = on_message_begin
,.on_header_field = on_header_field
,.on_header_value = on_header_value
,.on_url = on_url
,.on_status = 0
,.on_body = on_body
,.on_headers_complete = on_headers_complete
,.on_message_complete = on_message_complete
};

int
main (void)
{
const char *buf;
int i;
float start, end;
size_t parsed;

parser = malloc(sizeof(http_parser)); 分配一个http_parser

buf = "GET http://admin.omsg.cn/uploadpic/2016121034000012.png HTTP/1.1\r\nHost: admin.omsg.cn\r\nAccept: */*\r\nConnection: Keep-Alive\r\n\r\n";

start = (float)clock()/CLOCKS_PER_SEC;
for (i = 0; i < 1; i++) {
http_parser_init(parser, HTTP_REQUEST); 初始化parser为Request类型
parsed = http_parser_execute(parser, &settings_null, buf, strlen(buf)); 执行解析过程
}
end = (float)clock()/CLOCKS_PER_SEC;


buf="HTTP/1.1 200 OK\r\n"
"Date: Tue, 04 Aug 2009 07:59:32 GMT\r\n"
"Server: Apache\r\n"
"X-Powered-By: Servlet/2.5 JSP/2.1\r\n"
"Content-Type: text/xml; charset=utf-8\r\n"
"Connection: close\r\n"
"\r\n"
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
" <SOAP-ENV:Body>\n"
" <SOAP-ENV:Fault>\n"
" <faultcode>SOAP-ENV:Client</faultcode>\n"
" <faultstring>Client Error</faultstring>\n"
" </SOAP-ENV:Fault>\n"
" </SOAP-ENV:Body>\n"
"</SOAP-ENV:Envelope>";

http_parser_init(parser, HTTP_RESPONSE); 初始化parser为Response类型
parsed = http_parser_execute(parser, &settings_null, buf, strlen(buf)); 执行解析过程

free(parser);
parser = NULL;

printf("Elapsed %f seconds.\n", (end - start));

return 0;
}

结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
***MESSAGE BEGIN***

Url: http://admin.omsg.cn/uploadpic/2016121034000012.png
Header field: Host
Header value: admin.omsg.cn
Header field: Accept
Header value: */*
Header field: Connection
Header value: Keep-Alive

***HEADERS COMPLETE***


***MESSAGE COMPLETE***


***MESSAGE BEGIN***

Header field: Date
Header value: Tue, 04 Aug 2009 07:59:32 GMT
Header field: Server
Header value: Apache
Header field: X-Powered-By
Header value: Servlet/2.5 JSP/2.1
Header field: Content-Type
Found Content-Type
Header value: text/xml; charset=utf-8
http_png.type = text/xml; charset=utf-8
Header field: Connection
Header value: close

***HEADERS COMPLETE***

Elapsed 0.000091 seconds.

转载:https://www.cnblogs.com/arnoldlu/p/6497837.html