`
simohayha
  • 浏览: 1407104 次
  • 性别: 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请求头数据读取流程

    2. 请求头解析:Nginx遍历缓冲区中的数据,查找name-value对。每个请求头由一个名称和一个值组成,两者之间用冒号分隔,每个头占一行。例如,"Host: localhost"就是一个请求头。 3. 错误处理:如果请求头读取过程中...

    Nginx服务器中使用lua获取get或post参数.docx

    在处理请求体时,我们需要注意请求体的大小是否超过nginx配置中的client_body_buffer_size,如果超过的话,请求体将被缓冲到磁盘临时文件中。 此外,我们还可以使用ngx.req.get_body_file()来获取请求体的文件,...

    深入理解Nginx模块开发与架构解析(第2版)

    《深入理解Nginx模块开发与架构解析(第2版)》是一本专注于Nginx技术的专业书籍,针对Nginx的内部工作机制和模块开发进行了深入的探讨。本书旨在帮助读者掌握Nginx的核心概念、架构设计以及如何进行模块开发,从而...

    nginx 内置变量详解及隔离进行简单的拦截

    请求行中的name参数。 $args 请求行中参数字符串。 $cookie_name 名为name的cookie。 与$uri相同。 $http_name 任意请求头的值;变量名的后半部为转化为小写并且用下划线替代横线后的请求头名称。 $host “Host”...

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

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

    nginx源代码剖析

    3. **request请求的解析** Nginx在接收到HTTP请求后,会进行一系列的解析操作,包括解析请求行、头信息和请求体,然后根据这些信息决定如何处理请求。解析过程涉及到了网络协议栈的理解和HTTP规范的实现。 4. **...

    nginx正向代理与反向代理详解

    同时,需要配置`resolver`以指定DNS服务器,用于解析从内部网络收到的域名请求,获取目标服务器的IP地址,然后将请求转发到正确的外部服务器。 例如,配置文件中可能包含如下内容: ```nginx server { listen 80 ...

    第一个Nginx模块的例子

    - **理解模块结构**:每个模块通常包含解析配置文件的函数(如`ngx_module_init`)、处理请求的函数(如`ngx_http_request_handler`)等。 - **编写C代码**:Nginx模块用C语言编写,需要遵循Nginx的API和数据结构...

    reading-code-of-nginx-1.9.2_y123456yz.tar.gz

    同时,结合实际运行的网络请求,观察源码中的数据结构变化和控制流程,能够更好地理解Nginx的工作机制。 总结,Nginx的源码阅读不仅有助于提升对网络服务器设计的理解,也为开发自定义模块或优化现有功能提供了可能...

    统计Nginx日志里前一个小时的IP数量以及IOS占比

    首先,Nginx默认的日志格式通常包含以下字段:远程主机(remote_addr)、时间戳(time_local)、请求方法(request)、HTTP状态码(status)、请求大小(body_bytes_sent)、请求URI(request_uri)等。其中,时间戳...

    nginx源码分析--带注释

    在HTTP模块中,`ngx_http_request_t`结构体扮演着关键角色,它代表一个HTTP请求,并包含了请求的所有相关信息。注释中可能详细解释了这个结构体的各个字段及其作用,这对于理解Nginx如何处理请求生命周期非常有帮助...

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

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

    nginx源码vs工程-自定义handler处理-创建子请求处理逻辑-filter过滤器中处理应答

    子请求回调函数处理完后,激活父请求,继续往下阶段处理request的请求或者继续创建子请求转发数据。 4.在filter中获取后端服务器返回来的数据,处理完后,创建子请求转发到另外一个服务器处理,接收服务器回应数据...

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

    在本文中,我们将深入探讨如何使用Nginx服务器通过正则表达式拦截特定的URL请求。Nginx是一个高性能的Web服务器,它以其高效的静态文件处理能力和强大的反向代理功能而闻名。在许多Web应用程序架构中,Nginx被用作...

    Nginx源码剖析

    本文档深入探讨了 Nginx 的关键组成部分,包括其进程模型、内存管理、请求解析以及 Filter 的处理方式。这些机制共同确保了 Nginx 在高并发环境下的高效稳定运行。通过了解这些底层原理,我们可以更好地理解 Nginx ...

    Nginx 性能优化实践1

    在Nginx配置中,通过`location`指令和`proxy_pass`属性设置反向代理。例如,以下配置将所有/luban/开头的请求转发到本地8010端口: ``` location /luban/ { proxy_pass http://127.0.0.1:8010; } ``` 2. 负载均衡...

    NGINX conf 配置文件中的变量大全

    其配置文件是实现NGINX功能的核心,而变量则是配置文件中的关键元素,用于动态地响应请求或执行特定操作。 ### 重要变量详解 #### 1. $args $args 变量包含了请求URL中的查询字符串部分,即URL中“?”之后的所有...

    nginx 源代码 注释版

    通过阅读这个注释版的源代码,我们可以了解到Nginx是如何处理网络事件、解析配置、管理连接、处理HTTP请求以及实现各种高级功能的。对于想要定制Nginx、开发新模块或优化性能的开发者来说,这是一个不可多得的学习...

    Nginx经典教程

    预定义变量如`$http_user_agent`、`$request_uri`等,用于获取HTTP请求中的信息;环境变量则来源于操作系统或父进程;自定义变量允许开发者根据需要创建。 2. **变量解析**:解释了变量如何在配置文件中被解析和...

Global site tag (gtag.js) - Google Analytics