菜鸟学习nginx之接收HTTP请求行

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xxb249/article/details/85090336

上一篇介绍了HTTP会话建立流程,本篇介绍接收HTTP Header流程。由于Nginx是完全异步的,这对编写HTTP框架提出比较高的要求,因此Nginx在实现HTTP框架时定义出11个阶段。后续章节会详细介绍该11阶段。本篇介绍的接收HTTP Header请求在HTTP框架中是逻辑比较简单。

HTTP协议本身虽然比较简单,但是对于解析HTTP协议并不是很容易。体现之处就是HTTP协议header以及body都不是定长的。例如:上传文件可能是几个G大小,那么针对内存有限的前提下,如何做到高效呢?HTTP Header也是一样。接下来看一下Nginx是如何处理这种场景的,这是值得我们学习的。

一、HTTP报文处理简图

Nginx处理HTTP请求,大致分为以下几个流程(比较粗),我们做到心中有数即可,后续文章也是按照该图进行介绍。

二、第一次读取HTTP请求

在上一篇提到,当客户端HTTP请求事件发生后,处理该READ事件的回调函数是ngx_http_wait_request_handler。

2.1、流程图

2.2、源码

/**
 * 处理请求
 * @param rev 事件
 * 进入此函数,要么是http请求未到定时器超时了,要么是有http请求到来
 */
static void
ngx_http_wait_request_handler(ngx_event_t *rev)
{
    u_char *p;
    size_t size;
    ssize_t n;
    ngx_buf_t *b;
    ngx_connection_t *c;
    ngx_http_connection_t *hc;
    ngx_http_core_srv_conf_t *cscf;

    c = rev->data;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http wait request handler");
    /* http请求未到 定时器超时 */
    if (rev->timedout)
    {
        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
        ngx_http_close_connection(c);
        return;
    }

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

这段代码是处理定时器超时和连接异常关闭事件。当我们创建HTTP连接后,客户端始终没有发送HTTP请求到服务端,则会引起超时事件。那么Nginx就会关闭当前连接connection。

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

    size = cscf->client_header_buffer_size; //默认1024

    b = c->buffer;
    /* 为接收http header申请内存*/
    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;
    }
    /* 接收http报文 实际指向ngx_unix_recv */
    n = c->recv(c, b->last, size);

    if (n == NGX_AGAIN)
    {//需要重新设置定时器事件以及注册READ事件
        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;
    }

这段代码主要做了两件事儿:

1、根据配置,申请buffer,接收客户端发送的HTTP请求。

2、调用recv接口,接收客户端数据,根据返回值进行异常处理。

这里需要特别说明一下,由于HTTP Header是不定长,Nginx无法确定其长度(所有的web server都不能确定),这里默认先申请1k空间(最大8k)。当然这个配置可以通过nginx.conf进行修改。

    /* 表示接收到的字节数大于0 */
    b->last += n;

    if (hc->proxy_protocol)
    {
        hc->proxy_protocol = 0;

        p = ngx_proxy_protocol_read(c, b->pos, b->last);

        if (p == NULL)
        {
            ngx_http_close_connection(c);
            return;
        }

        b->pos = p;

        if (b->pos == b->last)
        {
            c->log->action = "waiting for request";
            b->pos = b->start;
            b->last = b->start;
            ngx_post_event(rev, &ngx_posted_events);
            return;
        }
    }

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

    ngx_reusable_connection(c, 0);
    /* 创建ngx_http_request对象 */
    c->data = ngx_http_create_request(c);
    if (c->data == NULL)
    {
        ngx_http_close_connection(c);
        return;
    }
    /* 设置http request-line handler函数 */
    rev->handler = ngx_http_process_request_line;
    ngx_http_process_request_line(rev);
}

创建request对象,并且设置读事件回调函数为ngx_http_process_request_line。最后进入ngx_http_process_request_line函数进行处理。这里为什么要把回调函数设置ngx_http_process_request_line,然后又进入ngx_http_process_request_line函数呢?原因只有一个:HTTP是不定长的。具体分析如下:

1、虽然在ngx_http_wait_request_handler函数中调用recv函数用于接收客户端请求,但是我们无法保证,此次recv操作一定能够接收到完整的request line。所以将READ事件回调函数设置为ngx_http_process_request_line。

2、但是通常情况下,request line并不是很长,所以一般场景下能够接收到,所以函数末尾进入ngx_http_process_request_line函数。在该函数ngx_http_process_request_line中就能确定是否已经接受完整request line。

三、接收Request Line

3.1、流程图

首先看一下,Request Line处理函数的流程图:

3.2、代码

/**
 * 处理请求行
 * @param rev 事件对象
 * 可能会多次进入此函数才能接收到完整的请求行
 */
static void
ngx_http_process_request_line(ngx_event_t *rev)
{
    ssize_t n;
    ngx_int_t rc, rv;
    ngx_str_t host;
    ngx_connection_t *c;
    ngx_http_request_t *r;

    c = rev->data;
    r = c->data;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
                   "http process request 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;
    }

    rc = NGX_AGAIN;

处理超时事件,直接关闭请求ngx_http_close_request。

    rc = NGX_AGAIN;

    for (;;)
    {

        if (rc == NGX_AGAIN)
        {
            n = ngx_http_read_request_header(r);//结构http request line 调用此函数

            if (n == NGX_AGAIN || n == NGX_ERROR)
            {
                return;
            }
        }
        /* 解析http 请求行 */
        rc = ngx_http_parse_request_line(r, r->header_in);

 尝试从网络中接收http报文,当成功接收报文进行HTTP请求行的解析ngx_http_parse_request_line。该函数就是纯粹的解析报文,内容比较枯燥此处不展开说明。

3.2.1、返回NGX_OK

        if (rc == NGX_OK)
        {

            /**
             * the request line has been parsed successfully 
             * 解析http request line成功 设置相关参数
             */
            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;
            }
            /* 解析uri */
            if (ngx_http_process_request_uri(r) != NGX_OK)
            {
                return;
            }

            if (r->host_start && r->host_end)
            {//处理host

                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)
            {//处理http版本小于1.0

                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;
            }
            /* 为request header申请内存 */
            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";
            /* 处理request header */
            rev->handler = ngx_http_process_request_headers;
            ngx_http_process_request_headers(rev);

            return;
        }

此处需要知道,事件处理函数变成ngx_http_process_request_headers,这个是重点内容。其他内容的设置比较简单明了。 

3.2.2、解析失败

        if (rc != NGX_AGAIN)
        {

            /* there was error while a request line parsing */

            ngx_log_error(NGX_LOG_INFO, c->log, 0,
                          ngx_http_client_errors[rc - NGX_HTTP_CLIENT_ERROR]);
            ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
            return;
        }

解析失败直接调用ngx_http_finalize_request函数 

3.2.3、返回NGX_AGAIN

        /* NGX_AGAIN: a request line parsing is still incomplete */

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

这里需要指明一点,由于HTTP报文是不定长的,所以申请的缓冲(1k)可能无法满足客户端发送过来的报文,因此这里进行缓冲区扩展--ngx_http_alloc_large_header_buffer。

四、ngx_http_close_request和ngx_http_finalize_request区别

本篇只介绍接收并且处理HTTP请求行处理流程,下一篇介绍Nginx处理HTTP Header部分。

猜你喜欢

转载自blog.csdn.net/xxb249/article/details/85090336