Nginx源代码分析之HTTP请求响应基本流程(十四)

原创 2016年08月29日 14:45:18

HTTP的处理请求流程我们从ngx_http_init_connection开始论述

里面注册了一个处理函数

rev->handler = ngx_http_wait_request_handler;

ngx_http_wait_request_handler的参数是ngx_event_t rev,一旦有请求到达,数据已经被复制到rev->data中,这时会调用ngx_http_process_request_line来处理请求数据


       但这里存在一个keepalive的问题,如果浏览器端设置了keepalive头部,那么r->keepalive标志为真,这样ngx在调用ngx_http_finalize_connection的时候,会调用ngx_http_set_keepalive设置keepalive的回调ngx_http_keepalive_handler,并保留当前的ngx_connection_t,然后直接返回。这样,在超时时间内,当同一个url请求再次到来的时候,i/o框架调用的是ngx_http_keepalive_handler而不是ngx_http_wait_request_handler。 

       还要补充的是,在http2中,因为socket默认是keepalive状态,而且多流并发,ngx_http_finalize_connection关闭的只是这个连接上的stream,因此会直接调用ngx_http_close_request然后返回:

#if (NGX_HTTP_V2)
    if (r->stream) {
        ngx_http_close_request(r, 0);
        return;
    }
#endif

而在ngx_http_close_request中,也不会关闭连接,而是关闭当前流:

#if (NGX_HTTP_V2)
    if (r->stream) {
        ngx_http_v2_close_stream(r->stream, rc);
        return;
    }
#endif


ngx_http_process_request_line的主要代码如下:

    rc = NGX_AGAIN;
    for ( ;; ) {
        if (rc == NGX_AGAIN) {
            n = ngx_http_read_request_header(r);
            if (n == NGX_AGAIN || n == NGX_ERROR) {
                return;
            }
        }
        rc = ngx_http_parse_request_line(r, r->header_in);
        if (rc == NGX_OK) {
            if (ngx_http_process_request_uri(r) != NGX_OK) {
                return;
            }
            if (r->host_start && r->host_end) {
                host.len = r->host_end - r->host_start;
                host.data = r->host_start;
                rc = ngx_http_validate_host(&host, r->pool, 0);

                if (rc == NGX_DECLINED) {
                    ngx_log_error(NGX_LOG_INFO, c->log, 0,
                                  "client sent invalid host in request line");
                    ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
                    return;
                }
                if (rc == NGX_ERROR) {
                    ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
                    return;
                }
                if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) {
                    return;
                }
                r->headers_in.server = host;
            }
            if (r->http_version < NGX_HTTP_VERSION_10) {
                if (r->headers_in.server.len == 0
                    && ngx_http_set_virtual_server(r, &r->headers_in.server)
                       == NGX_ERROR)
                {
                    return;
                }
                ngx_http_process_request(r);
                return;
            }
            c->log->action = "reading client request headers";
            rev->handler = ngx_http_process_request_headers;
            ngx_http_process_request_headers(rev);
            return;
        }
        if (r->header_in->pos == r->header_in->end) {

           rv = ngx_http_alloc_large_header_buffer(r, 1);
            if (rv == NGX_ERROR) {
                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
                return;
            }
            if (rv == NGX_DECLINED) {
                r->request_line.len = r->header_in->end - r->request_start;
                r->request_line.data = r->request_start;
                ngx_log_error(NGX_LOG_INFO, c->log, 0,
                              "client sent too long URI");
                ngx_http_finalize_request(r, NGX_HTTP_REQUEST_URI_TOO_LARGE);
                return;
            }
        }
    }

该函数的主要处理逻辑是按行读取r->connection->read中的数据,每成功读取到一行头部数据,再调用ngx_http_parse_request_line,根据http协议解析成http的头部,该函数的返回值如果是NGX_AGAIN,说明头部数据不完整,需要继续解析,说过返回NGX_OK,说明已经得到一个完整的头部,进入下一个处理流程。


首先调用ngx_http_process_request_uri处理url,接着调用ngx_http_set_virtual_server,该函数会根据已得到的host头部,在服务器初始化过程中配置的http{}中匹配一个server,其中关键代码如下:

    rc = ngx_http_find_virtual_server(r->connection,
                                      hc->addr_conf->virtual_names,
                                      host, r, &cscf);


    if (rc == NGX_ERROR) {
        ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return NGX_ERROR;
    }


    if (rc == NGX_DECLINED) {
        return NGX_OK;
    }


    r->srv_conf = cscf->ctx->srv_conf;
    r->loc_conf = cscf->ctx->loc_conf;


重点是ngx_http_find_virtual_server,检索当前配置的server表,找到对应的server。最后2行,把匹配的svr_conf,以及server里面的location表赋给ngx_http_request_t *r,这样,后面的执行流程就能通过ngx_http_request_t *r 调用相应模块的handler来执行正确的处理。


然后涉及到一个HTTP版本的问题,如果低于1.0.直接调用ngx_http_process_request,对于高于1.0请求,则调用ngx_http_process_request_headers,该函数的过程主要是循环调用ngx_http_parse_header_line来处理请求的所有头部,当ngx_http_parse_header_line返回NGX_HTTP_PARSE_HEADER_DONE的时候,再调用ngx_http_process_request。


ngx_http_process_request的主要功能则是调用ngx_http_handler,ngx_http_handler判断当前http请求是否是内部跳转,然后设置r->phase_handler。最后调用

ngx_http_core_run_phases,这个函数的功能是循环检查已经注册的处理阶段,并调用每个阶段的checker来执行本阶段需要完成的任务。


这里要回头解释一下nginx的阶段的的具体含义和原理,在ngx_http_core_module.h中定义了所有的阶段

typedef enum {
    NGX_HTTP_POST_READ_PHASE = 0,
    NGX_HTTP_SERVER_REWRITE_PHASE,
    NGX_HTTP_FIND_CONFIG_PHASE,
    NGX_HTTP_REWRITE_PHASE,
    NGX_HTTP_POST_REWRITE_PHASE,
    NGX_HTTP_PREACCESS_PHASE,
    NGX_HTTP_ACCESS_PHASE,
    NGX_HTTP_POST_ACCESS_PHASE,
    NGX_HTTP_TRY_FILES_PHASE,
    NGX_HTTP_CONTENT_PHASE,
    NGX_HTTP_LOG_PHASE
} ngx_http_phases;


nginx把整个请求响应划分为11个阶段,主要包括地址重定向,权限检查,请求文件,内容处理,日志记录等几个大的阶段,每个大的阶段分类下面可能有几个细阶段,比如重定向又分成NGX_HTTP_SERVER_REWRITE_PHASE,NGX_HTTP_FIND_CONFIG_PHASE,NGX_HTTP_REWRITE_PHASE,NGX_HTTP_POST_REWRITE_PHASE这几个阶段。我们知道,nginx有一个基本的核心模块ngx_http_core_module,还有其他许多功能模块,比如ngx_http_proxy_module,ngx_http_mp4_module等,以及第三方的可选模块。每个模块可以吧自己的处理函数注册到以上的阶段中。


阶段的初始化是在ngx_http.c的ngx_http_init_phases里面实现的,函数会初始化ngx_http_core_main_conf_t下面的ngx_http_phase_t的handlers数组:

    if (ngx_array_init(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers,
                       cf->pool, 4, sizeof(ngx_http_handler_pt))
        != NGX_OK)
    {
        return NGX_ERROR;
    }

接着,循环检查已注册的所有模块,调用模块的postconfiguration方法,该方法会把本模块的handler指针写到handlers数组里面。比如static module的postconfiguration是这样的

static ngx_int_t
ngx_http_static_init(ngx_conf_t *cf)
{
    ngx_http_handler_pt        *h;
    ngx_http_core_main_conf_t  *cmcf;


    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);


    h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
    if (h == NULL) {
        return NGX_ERROR;
    }


    *h = ngx_http_static_handler;


    return NGX_OK;
}

这些都准备好之后,会调用ngx_http_init_phase_handlers,他的作用是把已经注册的各阶段的处理函数和模块的处理函数写入到ngx_http_core_main_conf_t里面的phase_engine.handlers数据结构中,这样ngx_http_core_run_phases函数可以循环检查cmcf->phase_engine.handlers。然后判断是否执行这些阶段的处理函数:

    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);


    ph = cmcf->phase_engine.handlers;


    while (ph[r->phase_handler].checker) {


        rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);


        if (rc == NGX_OK) {
            return;
        }
    }

在前面的我们已经介绍过r->phase_handler是一个编号,其值一般是0,这样调用会从第一个注册的阶段的checker开始执行,每个checker内部会调用ph->handler,然后根据返回值决定r->phase_handler的值是递增还是直接指向ph-next。这样当前的checker调用结束后,会继续调用本阶段的handler或者下一个阶段的handler。一切都由phase_handler这个编号来的值来决定

当执行到NGX_HTTP_CONTENT_PHASE后,这个阶段的模块的函数一般会开始向客户端返回结果,这样HTTP流程就来到了响应阶段,如果是一个静态模块,响应比较简单,主要是根据请求的uri来读取本地文件或者缓存文件,然后填充响应包的头部和数据段,然后调用ngx_http_output_filter把数据发送出去。细节可以查看ngx_http_static_handler函数的代码。


版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

nginx学习笔记七(nginx HTTP框架的执行流程)

之前已经介绍过nginx的事件框架。那么,对于client发出的一个http的请求,nginx的http框架是如何一步步解析这个http请求?http框架又是如何和之前介绍过得epoll事件模块结合起...

菜鸟nginx源码剖析 框架篇(一) 从main函数看nginx启动流程

俗话说的好,牵牛要牵牛鼻子 驾车顶牛,处理复杂的东西,只要抓住重点,才能理清脉络,不至于深陷其中,不能自拔。对复杂的nginx而言,main函数就是“牛之鼻”,只要能理清main函数,就一定能理解其中...

Nginx学习之十一-Nginx启动框架处理流程

Nginx启动过程流程图 下面首先给出Nginx启动过程的流程图: ngx_cycle_t结构体 Nginx的启动初始化在src/core/nginx.c的main函数中完成,当然mai...

nginx的请求接收流程(二)

在ngx_http_process_request_line函数中,解析完请求行之后,如果请求行的uri里面包含了域名部分,则将其保持在请求结构的headers_in成员的server字段,heade...

文章18 :Nginx中http请求的处理过程

虽然我不想承认,但这篇文章的确是一篇很垃圾的博文。之所以垃圾 是因为没有考虑到Nginx的事件驱动对于请求处理的影响。建议各位看官去阅读  《http://tengine.taobao.org/bo...

nginx发起http请求

nginx发起http请求

TCP网络通讯如何解决分包粘包问题

TCP数据传输是以无边界的数据流传输形式,所谓无边界是指数据发送端发送的字节数,在数据接收端接受时并不一定等于发送的字节数,可能会出现粘包情况。 TCP粘包情况: 1. 发送端发送了数量比较的数据,接...

linux下在应用层打印调用堆栈

如下函数可以在任意函数中打印出当前的调用堆栈 输出到标准输出设备,一般就是命令行了 需要注意的是必须包含下面的标准库头文件 #include void print_trace(void)   { ...

TCP连接的状态详解以及故障排查

linux查看tcp的状态命令: 1)、netstat -nat 查看TCP各个状态的数量 2)、lsof -i:port 可以检测到打开套接字的状况 3)、 sar -n SOCK 查看tc...
  • hguisu
  • hguisu
  • 2014-08-20 07:06
  • 110582

关于TCP连接极端异常情况的处理方法的思考

这里说的极端异常情况,不是对方(服务器或者客户端)一般的异常情况,即引用该TCP连接的进程异常退出而由OS直接发出FIN或者RST包,从而关闭这个连接。这样本方会受到SOCKET_ERR返回,或者le...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)