leevis.com icon indicating copy to clipboard operation
leevis.com copied to clipboard

nginx的header大小设置

Open vislee opened this issue 7 years ago • 0 comments

概述

ngx提供了下面两个配置指令来设置可接收请求的header的大小。client_header_buffer_size默认大小为1k,对于大部分的请求够用了。如果有超大header的请求超过1k大小,nginx还会根据large_client_header_buffers默认4个8k的配置来分配8k内存处理,不会分配超过4块8k内存的。 请求包含了请求行和请求头还有body。 如果请求行大于large_client_header_buffers设置的size会返回414,也就是说请求行大于默认的8k就会返回414。 如果请求头的一个key value长度超过large_client_header_buffers设置的size会返回494,494可能是非标准code,nginx最终返回用户的是400,在body中会有"Request Header Or Cookie Too Large"类似的内容。 或者如果请求行和请求头超过large_client_header_buffers配置的num个size大小的内存也会返回494。

client_header_buffer_size size;
large_client_header_buffers number size;

代码分析

了解了这两个指令的意义后,来看下代码实现。http请求过来的时,nginx调用可读事件回调函数ngx_event_accept接收tcp请求,并调用ls->handler回调函数ngx_http_init_connection初始化http请求,该函数会把accept返回的fd可写回调函数设置为ngx_http_wait_request_handler。

当新的fd有可读事件就会调用ngx_http_wait_request_handler函数处理,该可读事件也就是客户端发送http请求引起的。 该函数先会分配一块大小为client_header_buffer_size大小的内存赋到r->header_in后调用recv函数接收请求的内容。接下来会调用ngx_http_process_request_line继续接收并解析http请求,并同时把可读事件回调函数也改为ngx_http_process_request_line。

调用ngx_http_read_request_header函数接收请求内容,然后调用ngx_http_parse_request_line函数解析请求行内容(GET/POST(流的组织(请求)方式) URL(地址+目录) 版本号)返回NGX_OK表示请求行解析完毕,返回 NGX_AGAIN表示请求行内容不完整。

解析完请求行以后,需要调用ngx_http_process_request_headers解析请求头,并修改可读事件回调函数。 ngx_http_process_request_headers函数会调用ngx_http_read_request_header函数读取请求头,调用ngx_http_parse_header_line解析请求头,返回NGX_HTTP_PARSE_HEADER_DONE说明请求头解析完毕。

上述解析请求行和解析请求头都会因为内容太长而保存内容的内存分配的太小。当出现这种情况后就会调用ngx_http_alloc_large_header_buffer根据large_client_header_buffers指令分配一块大的内存。

该函数ngx_http_alloc_large_header_buffer有两个形参,第一个为请求的request结构体,第二个为是否是请求行。 r->state是解析请求行和请求头用到的状态机,等于0时只能是解析请求行或请求头的开始,或成功解析请求行请求头结束,或成功解析一对key value请求头结束。

具体还是看下这个函数的实现吧。

static ngx_int_t
ngx_http_alloc_large_header_buffer(ngx_http_request_t *r,
    ngx_uint_t request_line)
{
    u_char                    *old, *new;
    ngx_buf_t                 *b;
    ngx_http_connection_t     *hc;
    ngx_http_core_srv_conf_t  *cscf;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "http alloc large header buffer");
    // 如果是刚开始解析请求行,则清理分配的内存返回。
    if (request_line && r->state == 0) {

        /* the client fills up the buffer with "\r\n" */

        r->header_in->pos = r->header_in->start;
        r->header_in->last = r->header_in->start;

        return NGX_OK;
    }

    // request_start 指向请求求行开始
    // header_name_start 指向一个key value请求头开始
    old = request_line ? r->request_start : r->header_name_start;

    cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);

    // 请求行太大 或 请求头的一个key value太大
    if (r->state != 0
        && (size_t) (r->header_in->pos - old)
                                     >= cscf->large_client_header_buffers.size)
    {
        return NGX_DECLINED;
    }

    hc = r->http_connection;

    if (hc->nfree) {
        b = hc->free[--hc->nfree];

        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "http large header free: %p %uz",
                       b->pos, b->end - b->last);

    } else if (hc->nbusy < cscf->large_client_header_buffers.num) {

        if (hc->busy == NULL) {
            hc->busy = ngx_palloc(r->connection->pool,
                  cscf->large_client_header_buffers.num * sizeof(ngx_buf_t *));
            if (hc->busy == NULL) {
                return NGX_ERROR;
            }
        }

        b = ngx_create_temp_buf(r->connection->pool,
                                cscf->large_client_header_buffers.size);
        if (b == NULL) {
            return NGX_ERROR;
        }

        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "http large header alloc: %p %uz",
                       b->pos, b->end - b->last);

    } else {
        return NGX_DECLINED;
    }

    hc->busy[hc->nbusy++] = b;

    // 解析完请求行
    if (r->state == 0) {
        /*
         * r->state == 0 means that a header line was parsed successfully
         * and we do not need to copy incomplete header line and
         * to relocate the parser header pointers
         */

        r->header_in = b;

        return NGX_OK;
    }

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "http large header copy: %uz", r->header_in->pos - old);

    new = b->start;

    // 复制整个请求行到新分配的内存
    // 或者复制一个key value请求头到新分配的内存
    ngx_memcpy(new, old, r->header_in->pos - old);

    b->pos = new + (r->header_in->pos - old);
    b->last = new + (r->header_in->pos - old);

    // 如果是解析请求行分配的,则需要重新修改指向解析请求行后的指针
    if (request_line) {
        r->request_start = new;

        if (r->request_end) {
            r->request_end = new + (r->request_end - old);
        }

        r->method_end = new + (r->method_end - old);

        r->uri_start = new + (r->uri_start - old);
        r->uri_end = new + (r->uri_end - old);

        if (r->schema_start) {
            r->schema_start = new + (r->schema_start - old);
            r->schema_end = new + (r->schema_end - old);
        }

        if (r->host_start) {
            r->host_start = new + (r->host_start - old);
            if (r->host_end) {
                r->host_end = new + (r->host_end - old);
            }
        }

        if (r->port_start) {
            r->port_start = new + (r->port_start - old);
            r->port_end = new + (r->port_end - old);
        }

        if (r->uri_ext) {
            r->uri_ext = new + (r->uri_ext - old);
        }

        if (r->args_start) {
            r->args_start = new + (r->args_start - old);
        }

        if (r->http_protocol.data) {
            r->http_protocol.data = new + (r->http_protocol.data - old);
        }

    } else {
        // 解析请求头,更新key value的指针
        r->header_name_start = new;
        r->header_name_end = new + (r->header_name_end - old);
        r->header_start = new + (r->header_start - old);
        r->header_end = new + (r->header_end - old);
    }

    r->header_in = b;

    return NGX_OK;
}

vislee avatar Mar 22 '17 09:03 vislee