nginx http框架流程(2) - 读取请求行、请求头

2018-07-28 11:46:33

Nginx 使用函数 ngx_http_process_request_line() 和 ngx_http_process_request_headers() 来读取并解析 http 请求行和请求头数据,这两个函数紧随着 ngx_http_wait_request_handler() 之后执行,由读事件直接触发。

函数 ngx_http_process_request_line() 使用无限for循环持续地从连接上读取数据,尝试获取HTTP请求行,工作流程的要点是:

读事件超时,直接结束请求
读取发生错误,直接结束请求
读不到数据( NGX_AGAIN),那么加入epoll 监控,等待下一次读事件
请求行解析出错,直接结束请求
请求行不完整,那么加入epoll监控,等待下一次读事件
请求行读取完毕,进入请求头处理函数继续处理

// 获取读事件相关的连接对象和请求对象
    c = rev->data;
    r = c->data;

    // 检查是否超时
    // 由定时器超时引发的,由ngx_event_expire_timers调用
    if (rev->timedout) {
        c->timedout = 1;
        ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
        return;
    }
for ( ;; ) {
    if (rc == NGX_AGAIN) {

        // 调用recv读数据,存在r->header_in里
        // 如果暂时无数据就加入定时器等待,加入读事件
        // 下次读事件发生还会进入这里继续读取
        // 返回读取的字节数量
        n = ngx_http_read_request_header(r);

        // again会继续读取,error则结束请求
        // again说明客户端发送的数据不足
        if (n == NGX_AGAIN || n == NGX_ERROR) {
            return;
        }
    }
    
    // 此时已经在r->header_in里有一些请求头的数据
    // 使用状态机解析,会调整缓冲区里的指针位置
    // 填充r->method、r->http_version
    // 如果数据不完整,无法解析则返回NGX_AGAIN,会再次读取
    rc = ngx_http_parse_request_line(r, r->header_in);
    
    // 成功解析出了http请求行
    if (rc == NGX_OK) {
    	
    	// 这里就是把请求行的数据赋值到 ngx_http_request_t 结构体里。
    	//...
    	
    	// 请求行处理完毕
        // 设置读事件处理函数为ngx_http_process_request_headers
        rev->handler = ngx_http_process_request_headers;

        // 立即调用ngx_http_process_request_headers,解析请求头
        ngx_http_process_request_headers(rev);

        return;
    }
    
    // 不是again则有错误
    if (rc != NGX_AGAIN) {
        if (rc == NGX_HTTP_PARSE_INVALID_VERSION) {
            ngx_http_finalize_request(r, NGX_HTTP_VERSION_NOT_SUPPORTED);

        } else {
            ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
        }
        return;
    }
// agian,请求行数据不完整
    // 看看是否缓冲区用完了
    // 如果满了说明数据很多,还在socket里但缓冲区太小放不下
    if (r->header_in->pos == r->header_in->end) {

        // 为接收http头数据分配一个大的缓冲区,拷贝已经接收的数据
        // 使用了hc->busy/free等成员
        rv = ngx_http_alloc_large_header_buffer(r, 1);

        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;
        }
        // 此时缓冲区已经足够大了
    }

    // 缓冲区没有满,那么大小是足够的,那么就再次尝试接收数据
    // 再次进入for循环,这时recv可能返回again,那么就等待下一次读事件即有数据可读
}

流程图大致如下


ngx_http_process_request_headers() 的处理逻辑与 ngx_http_process_request_line()  类似。

// 预设没有数据,需要重试
    rc = NGX_AGAIN;

    // 处理逻辑与ngx_http_process_request_line类似,也是无限循环
    for ( ;; ) {
        if (rc == NGX_AGAIN) {

            // 看看是否缓冲区用完了
            // 如果满了说明数据很多,还在socket里但缓冲区太小放不下
            if (r->header_in->pos == r->header_in->end) {

                // 为接收http头数据分配一个大的缓冲区,拷贝已经接收的数据
                // 使用了hc->busy/free等成员
                rv = ngx_http_alloc_large_header_buffer(r, 0);
            }

            // 调用recv读数据,存在r->header_in里
            // 如果暂时无数据就加入定时器等待,加入读事件
            // 下次读事件发生还会进入这里继续读取
            // 返回读取的字节数量
            n = ngx_http_read_request_header(r);

            // again说明客户端发送的数据不足
            if (n == NGX_AGAIN || n == NGX_ERROR) {
                return; //如果是 again,那么还会被读事件触发进入函数处理。
            }
        }
        
        // 此时已经读取了数据,存储在r->header_in里
        
        // 解析一行请求头,是否支持下划线由配置确定
        rc = ngx_http_parse_header_line(r, r->header_in,
                                        cscf->underscores_in_headers);
        
        if (rc == NGX_OK) {
        	// 把请求头加入链表
            h = ngx_list_push(&r->headers_in.headers);
            
            // ...
            // 这里会把请求头加入到链表和散列表
            // 赋值上解析出来的请求头数据
            
            // 成功解析完一行,继续循环,解析下一行
            continue;
        }
// 全部头解析完毕
    if (rc == NGX_HTTP_PARSE_HEADER_DONE) {

        //收到的请求数据总长度,即header+body
        r->request_length += r->header_in->pos - r->header_name_start;

        // 设置请求的状态,准备处理请求
        r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;

        // 检查收到的http请求头
        // http1.1不允许没有host头
        // content_length不能是非数字
        // 不支持trace方法
        // 如果是chunked编码那么长度头无意义
        // 设置keep_alive头信息
        rc = ngx_http_process_request_header(r);

        if (rc != NGX_OK) {
            return;
        }

        // 此时已经读取了完整的http请求头,可以开始处理请求了
        // 如果还在定时器红黑树里,那么就删除,不需要检查超时
        // 连接的读写事件handler都设置为ngx_http_request_handler
        // 请求的读事件设置为ngx_http_block_reading
        // 启动引擎数组,即r->write_event_handler = ngx_http_core_run_phases
        // 从phase_handler的位置开始调用模块处理
        // 如果有子请求,那么都要处理
        ngx_http_process_request(r);

        return;
    }
    // agian,请求行数据不完整
    if (rc == NGX_AGAIN) {
        // 继续循环,读取数据,保证解析一行成功
        continue;
    }

    ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
    return;
}

流程图大致如下


备注

1.测试环境centos7 64位,nginx版本为 1.14.0。
2..原文地址http://www.freecls.com/a/2712/e2


©著作权归作者所有
收藏
推荐阅读
简介
天降大任于斯人也,必先苦其心志。