欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

nginx中request请求的解析 博客分类: 服务器设计 nginxCC++C#UI

程序员文章站 2024-03-22 14:00:16
...

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 的格式:

引用


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

<entity-body>


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


引用
Method = "OPTIONS"
| "GET"
| "HEAD"
| "POST"
| "PUT"
| "DELETE"
| "TRACE"
| "CONNECT"
| extension-method
extension-method = token


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

引用


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



然后是version的格式:

引用
HTTP/<major>.<minor>



整个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的请求方法:

引用
Method = "OPTIONS"
| "GET"
| "HEAD"
| "POST"
| "PUT"
| "DELETE"
| "TRACE"
| "CONNECT"
| extension-method
extension-method = token


由于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;