nginx中request请求的解析

ngx_http_init_request 中初始化event 的handler 为ngx_http_process_request_line,然后首先调用ngx_http_read_request_header来读取头部,然后就是开始调用函数ngx_http_parse_request_line对request line进行解析。随后如果解析的url是complex的话,就进入complex的解析,最后进入headers的解析。


static void
ngx_http_process_request_line(ngx_event_t *rev)
{
................................................................

for ( ;; ) {

if (rc == NGX_AGAIN) {
//读取request头部
n = ngx_http_read_request_header(r);

if (n == NGX_AGAIN || n == NGX_ERROR) {
return;
}
}

//开始解析request请求头部。
rc = ngx_http_parse_request_line(r, r->header_in);
.............................................................................................................


if (r->complex_uri || r->quoted_uri) {

................................
//解析complex uri。

rc = ngx_http_parse_complex_uri(r, cscf->merge_slashes);
............................................

}
......................................................

//执行request header并且解析headers。
ngx_http_process_request_headers(rev);

}




这里nginx将request的解析分为三个部分,第一个是request-line部分,第二个是complex ui部分,第三个是request header部分。

在看nginx的处理之前,我们先来熟悉一下http的request-line的格式。

首先来看request 的格式:

[quote]

<method> <request-URL> <version>
<headers>

<entity-body>
[/quote]

其中第一行就是request-line,我们先来看
然后我们一个个来看,首先是method:


[quote]Method = "OPTIONS"
| "GET"
| "HEAD"
| "POST"
| "PUT"
| "DELETE"
| "TRACE"
| "CONNECT"
| extension-method
extension-method = token[/quote]

然后是URL,这里要注意这个是URL的完整格式,而我们如果和server直接通信的话,一般只需要path就够了。

[quote]

<scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>

[/quote]

然后是version的格式:

[quote]HTTP/<major>.<minor>[/quote]


整个request-line解析的一个状态,在ngx_http_parse.c中定义,这个状态保存在ngx_http_request_t 中的state中,它表示当前的request line解析到那一步了,其实也就是个状态机。

enum {
sw_start = 0,
sw_method,
sw_spaces_before_uri,
sw_schema,
sw_schema_slash,
sw_schema_slash_slash,
sw_host,
sw_port,
sw_after_slash_in_uri,
sw_check_uri,
sw_uri,
sw_http_09,
sw_http_H,
sw_http_HT,
sw_http_HTT,
sw_http_HTTP,
sw_first_major_digit,
sw_major_digit,
sw_first_minor_digit,
sw_minor_digit,
sw_spaces_after_digit,
sw_almost_done
} state;


而这里nginx这些状态就是为了解析这些东西。


接下来来通过代码片断看这些状态的含义。不过在看之前一定要

首先是start状态,也就是初始状态,我们刚开始解析Request的时候,就是这个状态。

case sw_start:
r->request_start = p;

//如果是回车换行则跳出switch,然后继续解析
if (ch == CR || ch == LF) {
break;
}
//不是A到Z的字母(大小写敏感的),并且不是_则返回错误
if ((ch < 'A' || ch > 'Z') && ch != '_') {
return NGX_HTTP_PARSE_INVALID_METHOD;
}
//到达这里说明下一步改解析方法了。因此下一个状态就是method
state = sw_method;
break;


然后是method状态,这个状态表示我们正在解析请求的method。
下面就是http的请求方法:

[quote]Method = "OPTIONS"
| "GET"
| "HEAD"
| "POST"
| "PUT"
| "DELETE"
| "TRACE"
| "CONNECT"
| extension-method
extension-method = token[/quote]

由于METHOD比较多,而且代码都比较重复,因此这里就看看几个代码片断.
由于


case sw_method:

//如果再次读到空格则说明我们已经准备解析request-URL,此时我们就能得到请求方法了。
if (ch == ' ') {
//先得到method的结束位置
r->method_end = p - 1;
//开始位置前面已经保存。
m = r->request_start;

//得到方法的长度,通过长度来得到具体不同的方法,然后给request的method赋值。
switch (p - m) {
case 3:
if (ngx_str3_cmp(m, 'G', 'E', 'T', ' ')) {
r->method = NGX_HTTP_GET;
break;
}

if (ngx_str3_cmp(m, 'P', 'U', 'T', ' ')) {
r->method = NGX_HTTP_PUT;
break;
}

break;
...............................................
}
//下一个状态准备开始解析URI
state = sw_spaces_before_uri;
break;
.......................................................................


然后是sw_spaces_before_uri状态,这里由于uri会有两种情况,一种是带schema的,一种是直接相对路径的(可以看前面的uri格式).


case sw_spaces_before_uri:

//如果是以/开始,则进入sw_after_slash_in_uri
if (ch == '/' ){
r->uri_start = p;
state = sw_after_slash_in_uri;
break;
}

c = (u_char) (ch | 0x20);
//如果是字母,则进入sw_schema处理
if (c >= 'a' && c <= 'z') {
r->schema_start = p;
state = sw_schema;
break;
}

switch (ch) {
//空格的话继续这个状态。
case ' ':
break;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;


sw_schema状态主要是用来解析协议类型。等到协议类型解析完毕则进入sw_schema_slash状态.


case sw_schema:

c = (u_char) (ch | 0x20);
//如果是字母则break,然后继续这个状态的处理。
if (c >= 'a' && c <= 'z') {
break;
}
//到这里说明schema已经结束。
switch (ch) {
//这里必须是:,如果不是冒号则直接返回错误。
case ':':
//设置schema_end,而start我们在上面已经设置过了
r->schema_end = p;
//设置下一个状态。
state = sw_schema_slash;
break;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;


sw_schema_slash和sw_schema_slash_slash是两个很简单的状态,第一个是得到schema的第一个/,然后进入sw_schema_slash_slash,而sw_schema_slash_slash则是得到了第二个/.然后进入sw_host。



case sw_schema_slash:
switch (ch) {
case '/':
//进入slash_slash
state = sw_schema_slash_slash;
break;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;
case sw_schema_slash_slash:
switch (ch) {
case '/':
//设置host的开始指针
r->host_start = p + 1;
//设置下一个状态为sw_host.
state = sw_host;
break;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;


然后是sw_host状态,这个状态用来解析host。


case sw_host:
c = (u_char) (ch | 0x20);
//这里很奇怪,不知道为什么不把判断写在一起。
if (c >= 'a' && c <= 'z') {
break;
}

if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '-') {
break;
}
//到达这里说明host已经得到,因此设置end指针。
r->host_end = p;

switch (ch) {
//冒号说明host有跟端口的,因此进入port状态。
case ':':
state = sw_port;
break;
//这个说明要开始解析path了。因此设置uri的start,然后进入slash_in_uri
case '/':
r->uri_start = p;
state = sw_after_slash_in_uri;
break;
//如果是空格,则设置uri的start和end然后进入http_09
case ' ':
r->uri_start = r->schema_end + 1;
r->uri_end = r->schema_end + 2;
state = sw_http_09;
break;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;


接下来是sw_port,这个状态用来解析协议端口。

case sw_port:
if (ch >= '0' && ch <= '9') {
break;
}
//如果到达这里说明端口解析完毕, 然后就来判断下一步需要的状态。
switch (ch) {
//如果紧跟着/,则说明后面是uri,因此进入uri解析,并设置port_end
case '/':
r->port_end = p;
r->uri_start = p;
state = sw_after_slash_in_uri;
break;
//如果是空格则设置port end,并进入http_09状态。
case ' ':
r->port_end = p;
r->uri_start = r->schema_end + 1;
r->uri_end = r->schema_end + 2;
state = sw_http_09;
break;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;


接下来是sw_after_slash_in_uri,sw_check_uri 这两个状态都是解析uri之前的状态,主要用于检测uri,比如complex uri等。

这里要对uri的格式比较熟悉,这里可以去看rfc3986,里面对uri的格式有比较清楚的描述。

因此我们主要来看sw_uri状态,这个状态就是开始解析uri。这里可以看到对http 0.9是特殊处理的,如果直接是回车或者换行的话,就进入http 0.9的处理。

case sw_uri:

if (usual[ch >> 5] & (1 << (ch & 0x1f))) {
break;
}

switch (ch) {
//下面三种情况都说明是http 0.9
case ' ':
r->uri_end = p;
state = sw_http_09;
break;
case CR:
r->uri_end = p;
r->http_minor = 9;
state = sw_almost_done;
break;
case LF:
r->uri_end = p;
r->http_minor = 9;
goto done;
//要对段进行解析。因此设置complex uri
case '#':
r->complex_uri = 1;
break;
case '\0':
r->zero_in_uri = 1;
break;
}
break;


接下来的sw_http_09,sw_http_H,sw_http_HT,sw_http_HTT,sw_http_HTTP, sw_first_major_digit,sw_major_digit,sw_first_minor_digit,sw_minor_digit,这几个状态主要是用来解析http的版本号的,都比较简单,这里就不仔细分析了。

然后来看最后两个状态sw_spaces_after_digit和sw_almost_done。

第一个状态表示已经解析完http状态了,然后发现有空格。


case sw_spaces_after_digit:
switch (ch) {
case ' ':
break;
//如果是回车,则进入almost_done,然后等待最后一个换行。
case CR:
state = sw_almost_done;
break;
//如果是换行则说明request-line解析完毕
case LF:
goto done;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;


最后是almost_done状态,也就是等待最后的换行。
case sw_almost_done:
r->request_end = p - 1;
switch (ch) {
case LF:
goto done;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
}


接下来是complex_uri的解析,这里比如段(#),比如win下的\\,等等,这里这个函数就不分析了,方法和request-line的差不多。

这里主要来看一下header的实现。

headers的格式我们知道一个name紧跟着一个冒号:,然后紧跟着一个可选的空格,然后是一个value,最后以一个CRLF结束,而headers的结束是一个CRLF。

下面是解析header时的状态列表:

    enum {
sw_start = 0,
sw_name,
sw_space_before_value,
sw_value,
sw_space_after_value,
sw_ignore_line,
sw_almost_done,
sw_header_almost_done
} state;


解析是ngx_http_parse_header_line来做的,这个函数一次只能解析一个header,如果传递进来的buf会有多个header,则是它只处理一个header,然后设置好对应的buf的域,等待下次再进行解析。

而这个函数能够返回三个值,第一个是NGX_OK,这个表示一个header解析完毕,第二个是NGX_AGAIN,表示header没有解析完毕,也就是说buf只有一部分的数据。这个时候,下次进来的数据会继续没有完成的解析。第三个是NGX_HTTP_PARSE_HEADER_DONE;,表示整个header已经解析完毕。

而对应的goto DONE表示NGX_OK,goto HEADER_DONE表示NGX_HTTP_PARSE_HEADER_DONE,而默认则是NGX_AFAIN.

这里有几个request的值需要先说明一下。

先来看request的域:

header_name_start 这个表示header_name的起始位置。
header_name_end 这个表示当前header_name的结束位置
header_start 这个是value的起始位置
header_end 这个是value的结束位置

header_hash 这个是header name的hash值。这个主要用来保存name和value到hash中。
lowcase_index 这个是索引值。

和上面一样,我们跟着代码来看这些状态的意义。


首先来看sw_start状态,这个是起始状态:

       
case sw_start:
// 设置header开始指针。
r->header_name_start = p;
r->invalid_header = 0;

//通过第一个字符的值来判断下一个状态。
switch (ch) {
//回车的话,说明没有header,因此设置状态为almost_done,然后期待最后的换行
case CR:
r->header_end = p;
state = sw_header_almost_done;
break;
case LF:
//如果换行则直接进入header_done,也就是整个header解析完毕
r->header_end = p;
goto header_done;
default:
//默认进入sw_name状态,进行name解析
state = sw_name;
//这里做了一个表,来进行大小写转换
c = lowcase[ch];

if (c) {
//得到hash值,然后设置lowcase_header,后面我会解释这两个操作的原因。
hash = ngx_hash(0, c);
r->lowcase_header[0] = c;
i = 1;
break;
}

r->invalid_header = 1;

break;

}
break;


然后是sw_name状态,这个状态进行解析name。



case sw_name:
//小写。
c = lowcase[ch];
//开始计算hash,然后保存header name
if (c) {
hash = ngx_hash(hash, c);
r->lowcase_header[i++] = c;
i &= (NGX_HTTP_LC_HEADER_LEN - 1);
break;
}

//如果存在下划线,则通过传递进来的参数来判断是否允许下划线,比如fastcgi就允许。
if (ch == '_') {
if (allow_underscores) {
hash = ngx_hash(hash, ch);
r->lowcase_header[i++] = ch;
i &= (NGX_HTTP_LC_HEADER_LEN - 1);

} else {
r->invalid_header = 1;
}

break;
}

//如果是冒号,则进入value的处理,由于value有可能前面有空格,因此先处理这个。
if (ch == ':') {
//设置header name的end。
r->header_name_end = p;
state = sw_space_before_value;
break;
}
//如果是回车换行则说明当前header解析已经结束,因此进入最终结束处理。
if (ch == CR) {
//设置对应的值。
r->header_name_end = p;
r->header_start = p;
r->header_end = p;
state = sw_almost_done;
break;
}

if (ch == LF) {
//设置对应的值,然后进入done
r->header_name_end = p;
r->header_start = p;
r->header_end = p;
goto done;
}
.......................................................

r->invalid_header = 1;

break;


sw_space_before_value状态就不分析了,这里它主要是解析value有空格的情况,并且保存value的指针。


case sw_space_before_value:
switch (ch) {
//跳过空格
case ' ':
break;
case CR:
r->header_start = p;
r->header_end = p;
state = sw_almost_done;
break;
case LF:
r->header_start = p;
r->header_end = p;
goto done;
default:
//设置header_start也就是value的开始指针。
r->header_start = p;
state = sw_value;
break;
}
break;



我们主要来看sw_value状态,也就是解析value的状态。



case sw_value:
switch (ch) {
//如果是空格则进入sw_space_after_value处理
case ' ':
r->header_end = p;
state = sw_space_after_value;
break;
//会车换行的话,说明header解析完毕进入done或者almost_done.也就是最终会返回NGX_OK
case CR:
r->header_end = p;
state = sw_almost_done;
break;
case LF:
r->header_end = p;
goto done;
}
break;


最后来看两个结束状态

    
//当前的header解析完毕
case sw_almost_done:
switch (ch) {
case LF:
goto done;
case CR:
break;
default:
return NGX_HTTP_PARSE_INVALID_HEADER;
}
break;
//整个header解析完毕
case sw_header_almost_done:
switch (ch) {
case LF:
goto header_done;
default:
return NGX_HTTP_PARSE_INVALID_HEADER;
}
}


最后来看这几个标记:

首先是默认,也就是当遍历完buf后,header仍然没有结束的情况:

此时设置对应的hash值,以及保存当前状态,以及buf的位置
b->pos = p;
r->state = state;
r->header_hash = hash;
r->lowcase_index = i;

return NGX_AGAIN;


然后是done,也就是当前的header已经解析完毕,此时设置状态为start,以及buf位置为p+1.
done:

b->pos = p + 1;
r->state = sw_start;
r->header_hash = hash;
r->lowcase_index = i;

return NGX_OK;


最后是header全部解析完毕,此时是得到了最后的回车换行,因此不需要hash值。
header_done:

b->pos = p + 1;
r->state = sw_start;

return NGX_HTTP_PARSE_HEADER_DONE;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值