nginx 源码分析:http 请求处理流程——读取请求

ngx_http_wait_request_handler

当客户端发送的请求到达时,epoll 监测到了一个读事件,然后调用读事件的 handler ngx_http_wait_request_handler 函数处理。它的原型如下:

static void ngx_http_wait_request_handler(ngx_event_t *rev);

函数开始

    c = rev->data;

    if (rev->timedout) {
        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
        ngx_http_close_connection(c);
        return;
    }

如果是由定时器触发的该读事件,说明客户端没有发送请求导致超时,那么直接调用 ngx_http_close_connection 关闭连接。后面很多 handler 函数里开头都是这样,后面我就不提了。

    if (c->close) {
        ngx_http_close_connection(c);
        return;
    }

如果该连接被要求强制关闭,则会关闭连接。

    hc = c->data;
    cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);

    size = cscf->client_header_buffer_size;

    b = c->buffer;

    if (b == NULL) {
        b = ngx_create_temp_buf(c->pool, size);
        if (b == NULL) {
            ngx_http_close_connection(c);
            return;
        }

        c->buffer = b;

    } else if (b->start == NULL) {

        b->start = ngx_palloc(c->pool, size);
        if (b->start == NULL) {
            ngx_http_close_connection(c);
            return;
        }

        b->pos = b->start;
        b->last = b->start;
        b->end = b->last + size;
    }

b = c->buffer 指向连接的缓冲区结构 ngx_buf_t,此时为NULL 。这结构体定义如下:


struct ngx_buf_s {
    u_char          *pos;
    u_char          *last;
    off_t            file_pos;
    off_t            file_last;

    u_char          *start;         /* start of buffer */
    u_char          *end;           /* end of buffer */
    ngx_buf_tag_t    tag;
    ngx_file_t      *file;
    ngx_buf_t       *shadow;


    /* the buf's content could be changed */
    unsigned         temporary:1;

    /*
     * the buf's content is in a memory cache or in a read only memory
     * and must not be changed
     */
    unsigned         memory:1;

    /* the buf's content is mmap()ed and must not be changed */
    unsigned         mmap:1;

    unsigned         recycled:1;
    unsigned         in_file:1;
    unsigned         flush:1;
    unsigned         sync:1;
    unsigned         last_buf:1;
    unsigned         last_in_chain:1;

    unsigned         last_shadow:1;
    unsigned         temp_file:1;

    /* STUB */ int   num;
};

这个结构体很重要。成员 start 和 end 表示缓冲区的起始和结束位置。而 post 和 last 则指向缓冲区中有效数据的起始和结束位置。start 和 post 之间的数据是被处理(已经分析过了)过的,可以认为是无效的。end 和 post 的差值表示缓冲区剩余空间大小。

ngx_create_temp_buf 函数创建了一个缓冲区,它的标志位 temporary 置1,表示缓冲区中的数据可以被修改。

    n = c->recv(c, b->last, size);
    
    if (n == NGX_AGAIN) {
    
        if (!rev->timer_set) {
            ngx_add_timer(rev, c->listening->post_accept_timeout);
            ngx_reusable_connection(c, 1);
        }
    
        if (ngx_handle_read_event(rev, 0) != NGX_OK) {
            ngx_http_close_connection(c);
            return;
        }
    
        /*
         * We are trying to not hold c->buffer's memory for an idle connection.
         */
    
        if (ngx_pfree(c->pool, b->start) == NGX_OK) {
            b->start = NULL;
        }
    
        return;
    }
    
    if (n == NGX_ERROR) {
        ngx_http_close_connection(c);
        return;
    }
    
    if (n == 0) {
        ngx_log_error(NGX_LOG_INFO, c->log, 0,
                      "client closed connection");
        ngx_http_close_connection(c);
        return;
    } 

调用 c->recv 指向的函数读取数据到缓冲区,这个函数我们上篇文章中说过了就是 ngx_unix_recv 函数。当它返回 NGX_AGAIN 函数时,表示暂时无法读取数据,那么再把定时器添加上,然后把分配的缓冲区释放掉。这就是为什么上文有 else if (b->start == NULL) 分支的原因。

当返回 NGX_ERROR 时,表示读取数据过程中出现错误,那么直接关闭连接。

当返回为 0 时,表示客户端主动关闭连接,那么我们也关闭连接。

当返回的 n 大于 0 时,表示读取到的数据长度。

其他的读取函数的返回值都是这几种情况,后面遇到类似的就不提了。

    b->last += n;

更新 last 指针,让他指向读取到的数据末尾。ngx_unix_recv 函数只是把数据读取到了缓冲区,并没有更新指针,所以这里要更新下。现在 post 和 last 之间就是读取到的数据了。

    ngx_reusable_connection(c, 0);

因为现在读取到了请求,所以将连接置为不可重用的状态。

    c->data = ngx_http_create_request(c);
    if (c->data == NULL) {
        ngx_http_close_connection(c);
        return;
    }

    rev->handler = ngx_http_process_request_line;
    ngx_http_process_request_line(rev);

调用 ngx_http_create_request 函数创建了一个表示请求的 ngx_http_request_t 结构,用 data 指向它。

然后将读事件的 handler 设置为 ngx_http_process_request_line 函数。

ngx_http_create_reques

这个函数的原型如下:

ngx_http_request_t *ngx_http_create_request(ngx_connection_t *c);

起始代码如下:

    hc = c->data;

    cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);

    pool = ngx_create_pool(cscf->request_pool_size, c->log);
    if (pool == NULL) {
        return NULL;
    }

    r = ngx_pcalloc(pool, sizeof(ngx_http_request_t));
    if (r == NULL) {
        ngx_destroy_pool(pool);
        return NULL;
    }

    r->pool = pool;

hc 指向 ngx_http_init_connection 函数中创建的 ngx_http_connection_t 结构。

然后调用 ngx_create_pool 创建一个内存池(请求使用的内存池),并调用 ngx_pcalloc 从内存池中分配一个 ngx_http_request_t 结构。其中 pool 成员指向内存池。

    r->http_connection = hc;
    r->signature = NGX_HTTP_MODULE;
    r->connection = c;

    r->main_conf = hc->conf_ctx->main_conf;
    r->srv_conf = hc->conf_ctx->srv_conf;
    r->loc_conf = hc->conf_ctx->loc_conf;

    r->read_event_handler = ngx_http_block_reading;

然后做一些初始化。重要的是设置了 main_conf、srv_conf、loc_conf 成员。它们分别指向默认 server{} 配置上下文的 main、srv、loc 级别的配置结构数组。

    r->header_in = hc->busy ? hc->busy->buf : c->buffer;

header_in 指向保存请求的缓冲区。

    if (ngx_list_init(&r->headers_out.headers, r->pool, 20,
                      sizeof(ngx_table_elt_t))
        != NGX_OK)
    {
        ngx_destroy_pool(r->pool);
        return NULL;
    }

初始化保存响应头部的链表。

    r->ctx = ngx_pcalloc(r->pool, sizeof(void *) * ngx_http_max_module);
    if (r->ctx == NULL) {
        ngx_destroy_pool(r->pool);
        return NULL;
    }

    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

    r->variables = ngx_pcalloc(r->pool, cmcf->variables.nelts
                                        * sizeof(ngx_http_variable_value_t));
    if (r->variables == NULL) {
        ngx_destroy_pool(r->pool);
        return NULL;
    }

分配保存所有模块上下文的数组。

分配保存所有变量的数组。

    r->main = r;
    r->count = 1;

    tp = ngx_timeofday();
    r->start_sec = tp->sec;
    r->start_msec = tp->msec;

请求的主请求指向自身。引用计数初始化为1。记录接受到请求的时间戳。

    r->method = NGX_HTTP_UNKNOWN;
    r->http_version = NGX_HTTP_VERSION_10;

    r->headers_in.content_length_n = -1;
    r->headers_in.keep_alive_n = -1;
    r->headers_out.content_length_n = -1;
    r->headers_out.last_modified_time = -1;

    r->uri_changes = NGX_HTTP_MAX_URI_CHANGES + 1;
    r->subrequests = NGX_HTTP_MAX_SUBREQUESTS + 1;

    r->http_state = NGX_HTTP_READING_REQUEST_STATE;

    ctx = c->log->data;
    ctx->request = r;
    ctx->current_request = r;
    r->log_handler = ngx_http_log_error_handler;	

    return r;

初始化一些成员。设置错误日志 handler 为 ngx_http_log_error_handler

ngx_http_process_request_line

ngx_http_wait_request_handler 函数最后调用 ngx_http_process_request_line 函数来读取请求行,它的定义如下:

static void ngx_http_process_request_line(ngx_event_t *rev);

这个函数里面是一个 for 循环:

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

循环调用 ngx_http_read_request_header 函数读请求行数据。然后调用 ngx_http_parse_request_line 函数解析请求行(它会一边解析数据时,一边更新缓冲区的 pos 指针)。当它返回 NGX_OK 时,说明请求行解析完成。

        if (rc == NGX_OK) {

            /* the request line has been parsed successfully */

            r->request_line.len = r->request_end - r->request_start;
            r->request_line.data = r->request_start;
            r->request_length = r->header_in->pos - r->request_start;

            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
                           "http request line: \"%V\"", &r->request_line);

            r->method_name.len = r->method_end - r->request_start + 1;
            r->method_name.data = r->request_line.data;

            if (r->http_protocol.data) {
                r->http_protocol.len = r->request_end - r->http_protocol.data;
            }

            if (ngx_http_process_request_uri(r) != NGX_OK) {
                return;
            }

r->request_line 就是缓冲区中请求行。

r->request_length 时目前已知的请求行长度。

r->method_name 就是请求方法。

r->http_protocol 就是HTTP协议版本号。

然后调用 ngx_http_process_request_uri 函数处理 url,分离出 uri,参数,请求的文件扩展名。

            if (ngx_list_init(&r->headers_in.headers, r->pool, 20,
                              sizeof(ngx_table_elt_t))
                != NGX_OK)
            {
                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
                return;
            }

            c->log->action = "reading client request headers";

            rev->handler = ngx_http_process_request_headers;
            ngx_http_process_request_headers(rev);

            return;
        }

接着初始化保存请求头部的链表,每个请求头用 ngx_table_elt_t 结构表示。

typedef struct {
    ngx_uint_t        hash;
    ngx_str_t         key;
    ngx_str_t         value;
    u_char           *lowcase_key;
} ngx_table_elt_t;

key 是请求头部名称,value 是请求头部的值,lowcase_key 指向小写的头部名称,hash 是小写头部名称计算的 hash 值。

然后将读事件的 handler 设置为 ngx_http_process_request_headers,并调用。

ngx_http_process_request_headers

这个函数与 ngx_http_process_request_line 类似,函数体里是一个大循环。

    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

    rc = NGX_AGAIN;

    for ( ;; ) {

        if (rc == NGX_AGAIN) {

            if (r->header_in->pos == r->header_in->end) {

                rv = ngx_http_alloc_large_header_buffer(r, 0);

如果缓冲区满了,则调用 ngx_http_alloc_large_header_buffer 分配一个更大的缓冲区。

            n = ngx_http_read_request_header(r);

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

它返回 NGX_OK ,说明大的缓冲区分配成功。还是调用 ngx_http_read_request_header 读取请求头部,n 是读取到的数据长度。

        rc = ngx_http_parse_header_line(r, r->header_in,
                                        cscf->underscores_in_headers);

        if (rc == NGX_OK) {

            r->request_length += r->header_in->pos - r->header_name_start;
            /* a header line has been parsed successfully */

            h = ngx_list_push(&r->headers_in.headers);
            if (h == NULL) {
                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
                return;
            }

            h->hash = r->header_hash;

            h->key.len = r->header_name_end - r->header_name_start;
            h->key.data = r->header_name_start;
            h->key.data[h->key.len] = '\0';

            h->value.len = r->header_end - r->header_start;
            h->value.data = r->header_start;
            h->value.data[h->value.len] = '\0';

            h->lowcase_key = ngx_pnalloc(r->pool, h->key.len);
            if (h->lowcase_key == NULL) {
                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
                return;
            }

            if (h->key.len == r->lowcase_index) {
                ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len);

            } else {
                ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
            }


调用 ngx_http_parse_header_line 函数解析一个请求头部。当它返回 NGX_OK 时,说明成功解析到一个请求头部。那么更新 r->request_length 长度。填充好 ngx_table_elt_t 结构,将它加入 r->headers_in.headers 链表中。

            hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash,
                               h->lowcase_key, h->key.len);

            if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
                return;
            }

            continue;
        }

接下来,根据请求头部名称,找到处理它的 handler。ngx_hash_find 在这里返回一个 ngx_http_header_t 结构,这些结构体定义在全局变量 ngx_http_headers_in 中。

ngx_http_header_t  ngx_http_headers_in[] = {
    { ngx_string("Host"), offsetof(ngx_http_headers_in_t, host),
                 ngx_http_process_host },
    ...

在解析完 http{} 配置时,ngx_http_init_headers_in_hash 函数会被调用将它们组织成一个 hash 表(cmcf->headers_in_hash)。

这里就讲一个最关键的 handler ,就是处理 Host 头部的函数 ngx_http_process_host

ngx_http_process_host

    host = h->value;

    rc = ngx_http_validate_host(&host, r->pool, 0);

它先调用 ngx_http_validate_host 检查 Host 的头部的值是否是一个合法的值。

    if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) {
        return NGX_ERROR;
    }

    r->headers_in.server = host;

    return NGX_OK;

然后调用 ngx_http_set_virtual_server 函数,根据 Host 头部的值,查找匹配的虚拟服务。

ngx_http_set_virtual_server

    hc = r->http_connection;

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

它调用 ngx_http_find_virtual_server 函数查找匹配的虚拟服务。监听的IP和端口号相关的所有服务都被组织成了一个 hash 表,这个函数里就是调用 ngx_hash_find_combined 函数查找 hash 表,而这个 hash 表支持通配匹配,它的查找规则是:

  1. 先匹配精确的形式。
  2. 再匹配最长的前缀统配的形式。
  3. 再匹配最长的后缀通配的形式。

如果都不匹配,ngx_http_find_virtual_server 函数再查找是否有匹配的正则表达式的形式。

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

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

    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

    ngx_set_connection_log(r->connection, clcf->error_log);

    return NGX_OK;

如果 ngx_http_find_virtual_server 返回 NGX_DECLINED,表示没有匹配的服务,直接返回。这样就会使用默认服务。

如果返回 NGX_OK,说明找到了匹配的服务。那么将 srv_conf 和 loc_conf 设置为解析这个匹配的 server{} 时,创建的 srv 和 loc 级别的配置结构数组。也就是,之后处理这个请求时都使用这个 server {} 里的配置项。

到这里,我们就看到了一个完整的匹配虚拟服务的流程。

ngx_http_process_request_headers

让我们又回到这个函数。当 ngx_http_parse_header_line 返回 NGX_HTTP_PARSE_HEADER_DONE 时,表示所有的请求头部都已经解析完。

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

            if (rc != NGX_OK) {
                return;
            }

            ngx_http_process_request(r);

            return;
        }

那么它会调用 ngx_http_process_request_header 函数来检查头部的使用是否符合 HTTP 协议规范。如果是的话就调用 ngx_http_process_request 开始处理请求。

下篇文章继续。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值