`
simohayha
  • 浏览: 1386259 次
  • 性别: Icon_minigender_1
  • 来自: 火星
社区版块
存档分类
最新评论

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

引用


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









0
0
分享到:
评论
1 楼 trulliandloeb 2014-01-16  
请问类似于ngx_str3_cmp(m, 'G', 'E', 'T', ' ')这种比较,各种不同的编码都能行吗?

相关推荐

    如何利用nginx通过正则拦截指定url请求详解

    我们知道nginx会对请求进行解析,然后回得到关于请求的url等信息,我们只需要对url进行匹配,然后拦截即可。 匹配规则 location / { if ($request_uri ~* ^/\?http(.*)$) { return 404; } } 经过这样的匹配,...

    详解Nginx的配置函数对于请求体的读取

    nginx核心本身不会主动读取请求体,这个工作是交给请求处理阶段的模块来做,但是nginx核心提供了ngx_http_read_client_request_body()接口来读取请求体,另外还提供了一个丢弃请求体的接口-ngx_...

    详解Nginx的核心配置模块中对于请求体的接受流程

    本篇文章主要会介绍nginx中请求的接收流程,包括请求头的解析和请求体的读取流程。 首先介绍一下rfc2616中定义的http请求基本格式: Request = Request-Line *(( general-header | request-header | entity-...

    Nginx源码剖析

    本书详细介绍了Nginx的进程模型,内存管理,request请求的解析,handler的处理等

    配置Nginx+PHP的正确思路与过程

    假设我们用PHP实现了一个前端控制器,或者直白点说就是统一入口:把PHP请求都发送到同一个文件上,然后在此文件里通过解析「REQUEST_URI」实现路由。 一般这样配置 此时很多教程会教大家这样配置Nginx+PHP: server...

    常见的Nginx配置误区

    假设我们用PHP实现了一个前端控制器,或者直白点说就是统一入口:把PHP请求都发送到同一个文件上,然后在此文件里通过解析「REQUEST_URI」实现路由。此时很多教程会教大家这样配置Nginx+PHP: 代码如下:server { ...

    如何正确配置Nginx + PHP

    假设我们用PHP实现了一个前端控制器,或者直白点说就是统一入口:把PHP请求都发送到同一个文件上,然后在此文件里通过解析「REQUEST_URI」实现路由。 一般这样配置 此时很多教程会教大家这样配置Nginx+PHP: ...

    Nginx配置并兼容HTTP实现代码解析

    CSR:Cerificate Signing Request,证书签署请求文件,里面包含申请者的 DN(Distinguished Name,标识名)和公钥信息,在第三方证书颁发机构签署证书的时候需要提供。证书颁发机构拿到 CSR 后使用其根证书私钥对...

    深入理解Node.js的HTTP模块

    http.request则是一个HTTP客户端工具,用于向HTTP服务器发送请求,实现内容抓取。 一. HTTP服务器 http.Server提供一套封装级别很低的API,仅仅是流控制和简单的消息解析。 可以使用http.CreateServer()来创建一个...

Global site tag (gtag.js) - Google Analytics