nginx的http request的初始化

这里先插入一张我找到的很好的一张图,描述了request初始化的整个过程:

从这篇文章开始正式进入http部分了,首先应该着手的是nginx是如何初始化http request的,我们在前面的文章,我们知道当接收到一个connection后,nginx首先会调用ngx_listening_t的handler来处理这个connection,这个handler函数是ngx_http_init_connection,好我们就从这个函数入手(Ngx_http_request.c):

//初始化日志相关的东西
    ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t));
    if (ctx == NULL) {
        ngx_http_close_connection(c);
        return;
    }

    ctx->connection = c;
    ctx->request = NULL;
    ctx->current_request = NULL;

    c->log->connection = c->number;
    c->log->handler = ngx_http_log_error;
    c->log->data = ctx;
    c->log->action = "reading client request line";

    c->log_error = NGX_ERROR_INFO;
上述代码首先是初始化日志相关的东西,
    rev = c->read;
    rev->handler = ngx_http_init_request;   //当前connection的读事件的处理函数,也就是说连接进来的connection的第一次读事件用这个函数进行处理
    c->write->handler = ngx_http_empty_handler;   //当前connection的写事件的处理函数
紧接着的代码是设置当前connection的读写事件的handler,我们主要看写事件,它的处理函数是ngx_http_init_request。
    if (rev->ready) {  //表示当前的已经可读了,那么可以直接用ngx_http_init_request来处理,这里如果使用的是epoll的话,一般是不会执行的
        /* the deferred accept(), rtsig, aio, iocp */
									
        if (ngx_use_accept_mutex) {
            ngx_post_event(rev, &ngx_posted_events);			//这里因为还在accept中,所以可能会持有锁,因此要延后处理
            return;
        }

        ngx_http_init_request(rev);
        return;
    }
接下来就是如果使用的是aio等模型,那么现在就可以用 ngx_http_init_request来处理写事件了,但是我们知道一般情况下用的是epoll,所以这里一般是不会执行的。
    ngx_add_timer(rev, c->listening->post_accept_timeout);   //为读事件设置定时器,超时的时候会自动调用该事件的处理函数ngx_http_init_request,如果是超时调用的话会直接关闭当前的connection

    if (ngx_handle_read_event(rev, 0) != NGX_OK) {    //将事件挂起来,把它放到epoll中
#if (NGX_STAT_STUB)
        (void) ngx_atomic_fetch_add(ngx_stat_reading, -1);
#endif
        ngx_http_close_connection(c);
        return;
    }
上述的代码首先是为读事件设置了timer,当超时的时候会自动调用 ngx_http_init_request函数,如果是超时的话,这个时候nginx会关闭这个连接。ngx_handle_read_event函数的作用是将当前的读事件加入到epoll当中去。

好了,接下来我们可以来看读事件的处理函数了ngx_http_init_request:

 c = rev->data;   //获取相应的connection

    if (rev->timedout) {  //表示读取已经超时了,直接关闭connection
        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");

        ngx_http_close_connection(c);  //关闭connection
        return;
    }
首先是获取事件对应的connection,并且判断当前是否是已经超时了,如果超时的话,那么直接关闭掉这个connection。
    c->requests++;  //request计数加1

    hc = c->data;  //创建http connection

    if (hc == NULL) {
        hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));
        if (hc == NULL) {
            ngx_http_close_connection(c);
            return;
        }
    }

    r = hc->request;  //获取http请求结构

    if (r) {
        ngx_memzero(r, sizeof(ngx_http_request_t));

        r->pipeline = hc->pipeline;

        if (hc->nbusy) {
            r->header_in = hc->busy[0];
        }

    } else {
        r = ngx_pcalloc(c->pool, sizeof(ngx_http_request_t));   //为请求结构分配内存空间
        if (r == NULL) {
            ngx_http_close_connection(c);
            return;
        }

        hc->request = r;
    }

    c->data = r;   //将connection的data置为当前的request请求
    r->http_connection = hc;   //请求所在的http connection

    c->sent = 0;
    r->signature = NGX_HTTP_MODULE;
上述代码用来创建http request结构,以及http connection,并且进行一些初始化。
 /* find the server configuration for the address:port */

    /** 
     * ngx_listening_t的servers存放监听同一端口的server,但它们的监听的地址可以不同。 
     *  
     *            port 
     *         /   |   \ 
     *          addr1 addr2 addr3       
     *           |     |     | 
     *          conf1 conf2  conf3 
     */  

    port = c->listening->servers;  

    r->connection = c;  //当前请求的connection为c

    if (port->naddrs > 1) {

        /*
         * there are several addresses on this port and one of them
         * is an "*:port" wildcard so getsockname() in ngx_http_server_addr()
         * is required to determine a server address
         */
			/* 获取连接c的socket绑定的本地地址 */  
        if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) {
            ngx_http_close_connection(c);
            return;
        }
		/* 根据连接c的socket地址匹配port->addrs,找到对应的address:port */	
        switch (c->local_sockaddr->sa_family) {

#if (NGX_HAVE_INET6)
        case AF_INET6:
            sin6 = (struct sockaddr_in6 *) c->local_sockaddr;

            addr6 = port->addrs;

            /* the last address is "*" */

            for (i = 0; i < port->naddrs - 1; i++) {
                if (ngx_memcmp(&addr6[i].addr6, &sin6->sin6_addr, 16) == 0) {
                    break;
                }
            }

            addr_conf = &addr6[i].conf;

            break;
#endif

        default: /* AF_INET */
            sin = (struct sockaddr_in *) c->local_sockaddr;

            addr = port->addrs; //监听当前port的所有地址结构

            /* the last address is "*" */

            for (i = 0; i < port->naddrs - 1; i++) {
                if (addr[i].addr == sin->sin_addr.s_addr) {
                    break;
                }
            }

            addr_conf = &addr[i].conf;

            break;
        }

    } else {

        switch (c->local_sockaddr->sa_family) {

#if (NGX_HAVE_INET6)
        case AF_INET6:
            addr6 = port->addrs;
            addr_conf = &addr6[0].conf;
            break;
#endif

        default: /* AF_INET */
            addr = port->addrs;
            addr_conf = &addr[0].conf;
            break;
        }
    }

    r->virtual_names = addr_conf->virtual_names;

    /* the default server configuration for the address:port */
    cscf = addr_conf->default_server;   //获取当前地址结构所在的ngx_http_core_srv_conf_t结构

    r->main_conf = cscf->ctx->main_conf;  //获取main_conf
    r->srv_conf = cscf->ctx->srv_conf;  //获取srv_conf
    r->loc_conf = cscf->ctx->loc_conf;  //获取loc_conf
上述代码,其实英文的注释就已经说的很清楚了,根据add:port的信息,来找到对应的server配置,因为我们知道对于同一个端口,可以有好几个地址在监听。
   rev->handler = ngx_http_process_request_line;   //将读事件的处理函数设置为ngx_http_process_request_line,用于处理请求行
    r->read_event_handler = ngx_http_block_reading;
接下来就是将当前event的handler设置为ngx_http_process_request_line函数,以及设置request的读事件,接下来的代码还会涉及到初始化nginx变量,为heade_in分配buffer,用来存储头部信息等等操作,最后就是会有如下代码:
    rev->handler(rev);   //调用ngx_http_process_request_line函数来处理
嗯,说白了就是调用 ngx_http_process_request_line函数。那好,接下来我们来看ngx_http_process_request_line函数,该函数式用来处理请求的行的,例如GET /index.php HTTP/1.1
    c = rev->data;   //获取相应的connection
    r = c->data;    //获取request结构 (这个时候的connection的data域已经被设置为request了)

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
                   "http process request line");
//如果已经超时了,那么关闭就ok了
    if (rev->timedout) {
        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
        c->timedout = 1;
        ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
        return;
    }
代码首先是获取对应的connection,以及request结构,并要判断是否是超时了,如果超时了,那么可以直接关闭connection。
//死循环,不断调用ngx_http_read_request_header函数来从socket中读取足够的信息,然后解析出HTTP Method、URI以及HTTP版本等信息。
    for ( ;; ) {

        if (rc == NGX_AGAIN) {
			 /** 
    * 读取请求行,保存到r->header_in缓冲区 
    */  
            n = ngx_http_read_request_header(r);

            if (n == NGX_AGAIN || n == NGX_ERROR) {
                return;
            }
        }
		/** 
		 * 解析请求行,解析后的信息的是以r->uri_start,r->uri_end,r->arg_start等 
		 * 一些指针存储的,这些指针指向r->header_in 
		 */  
        rc = ngx_http_parse_request_line(r, r->header_in);
		
接下来程序进入了死循环,不断的从socket中读取信息保存到header_in缓冲当中,来初始化http的method,url以及参数,版本信息,调用ngx_http_parse_request_line函数来读取信息,调用ngx_http_parse_request_line函数来解析这些信息。接下来的代码还会涉及到对uri_start等指针在buffer中的位置进行设置,这里就不贴代码了,最后
rev->handler = ngx_http_process_request_headers;   //调用ngx_http_process_request_headers来处理header
            ngx_http_process_request_headers(rev);
设置读事件的handler为函数,并调用该函数。

好吧,接下来我们来看ngx_http_process_request_headers函数,前面的ngx_http_process_request_line函数已经搞定了最初的请求数据,那么ngx_http_process_request_headers函数是用来处理请求的header信息的,

    c = rev->data;   //获取connection
    r = c->data;    //获取request

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
                   "http process request header line");
//如果超时的话,那么直接关闭
    if (rev->timedout) {
        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
        c->timedout = 1;
        ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
        return;
    }
函数首先还是获取相应的connection以及request结构,然后再判断是否已经超时了,
//获取配置
    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
    cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
接下来获取一些配置结构。
/每一次读取解析一个header
    for ( ;; ) {

        if (rc == NGX_AGAIN) {
    
            n = ngx_http_read_request_header(r);   //读取header,然后保存在header_in 当中

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

        rc = ngx_http_parse_header_line(r, r->header_in,
                                        cscf->underscores_in_headers);    //解析header的一行
接下来还是进入for循环来读取header信息,这里还涉及到当buffer不够用扩大的代码,没有贴出来。调用ngx_http_read_request_header函数读取header信息,并保存在header_in缓冲当中,接着调用ngx_http_parse_header_line函数来解析刚刚读取的一条header信息。接下来还会涉及到对解析出来的header信息进行处理的代码,将其变成key-value的结构,并将其保存到headers_in的header链表中存储起来。
if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
            /* a whole header has been parsed successfully */
            r->request_length += r->header_in->pos - r->header_name_start;

            r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;

            rc = ngx_http_process_request_header(r);  ///* 根据host匹配虚拟主机,并对一些header初始化 */  ,说白了就是对头部的一些效验
            if (rc != NGX_OK) {
                return;
            }
            ngx_http_process_request(r);  // 真正的处理request

            return;
        }
最后,当header相关的处理完成了以后,先调用ngx_http_process_request_header函数对header进行一些效验,然后调用ngx_http_process_request函数真正的开始处理request。

好了,接下来我们来看ngx_http_process_request函数吧,该函数很简单,首先是将以前设置的定时器删除,然后

    c->read->handler = ngx_http_request_handler;  //大概是处理子请求的吧
    c->write->handler = ngx_http_request_handler;
    r->read_event_handler = ngx_http_block_reading;
 
    ngx_http_handler(r);   //为跑一遍所有phase hander做准备。
//处理subrequest
    ngx_http_run_posted_requests(c);
设置读事件的handler,这些貌似是用于处理子请求的,然后调用ngx_http_handler函数来处理这个request,准备跑一边所有的phase。

好,接下来来看ngx_http_handler函数:

//初始化phase handler
    if (!r->internal) {
        switch (r->headers_in.connection_type) {
        case 0:
            r->keepalive = (r->http_version > NGX_HTTP_VERSION_10);
            break;

        case NGX_HTTP_CONNECTION_CLOSE:
            r->keepalive = 0;
            break;

        case NGX_HTTP_CONNECTION_KEEP_ALIVE:
            r->keepalive = 1;
            break;
        }

        r->lingering_close = (r->headers_in.content_length_n > 0);
        r->phase_handler = 0;

    } else {
        cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
        r->phase_handler = cmcf->phase_engine.server_rewrite_index;   //设置当前所在的phase handler
    }
首先是初始化request的phase handler,接下来就是
    r->write_event_handler = ngx_http_core_run_phases;   
    ngx_http_core_run_phases(r);   //调用ngx_http_core_run_phases跑一遍phase
调用ngx_http_core_run_phases函数来跑一边phase。接下来来看 ngx_http_core_run_phases函数:
//该函数用于对request跑一遍phase
void
ngx_http_core_run_phases(ngx_http_request_t *r)
{
    ngx_int_t                   rc;
    ngx_http_phase_handler_t   *ph;
    ngx_http_core_main_conf_t  *cmcf;

    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);  //获取ngx_http_core_main_conf_t

    ph = cmcf->phase_engine.handlers;   //获取所有的phase handler
    
	/* 遍历phase上注册的所有handler,这里是以r->phase_handler为索引组成的链表 */  
    while (ph[r->phase_handler].checker) {

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

        if (rc == NGX_OK) {
            return;
        }
    }
}
该函数很简单,因为nginx把整个http的分成了11个phase,每一个phase都有自己的handler,那么我们按照这个顺序调用完以后,相当于这个http连接也就完成了。

下一篇文章来讲nginx的http phase相关的东西吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值