Heim >Backend-Entwicklung >PHP-Tutorial >Umgang mit HTTP-Anfragen in Nginx

Umgang mit HTTP-Anfragen in Nginx

WBOY
WBOYOriginal
2016-08-08 09:30:17780Durchsuche

Übersicht

Während des ersten Startvorgangs von Nginx ruft der Worker-Workerprozess die ngx_event_process_init-Methode des Ereignismoduls auf Jeder Listener-Socket ngx_listening_t weist eine ngx_connection_t-Verbindung zu und legt die Rückrufmethode für Leseereignisse auf der Verbindung fest handler ist ngx_event_accept, und das Leseereignis wird an den epoll-Ereignismechanismus gemountet, um darauf zu warten, dass das lesbare Ereignis an der Listening-Socket-Verbindung auftritt , Nginx kann Anfragen von Kunden empfangen und verarbeiten. Wenn auf der lauschenden Socket-Verbindung ein lesbares Ereignis auftritt, d. h. wenn eine Verbindungsanforderung vom Client für die Verbindung vorliegt, wird die Handler-Rückrufmethode ngx_event_accept des Leseereignisses gestartet . Die Funktion accept() wird in der Methode ngx_event_accept aufgerufen, um die Verbindungsanfrage vom Client zu empfangen. Nachdem die Verbindung erfolgreich hergestellt wurde, wird die Funktion ngx_event_accept aufgerufen die Callback-Methode handler ls->handler(c) (die Rückrufmethode ist ngx_http_init_connection). Nachdem die Verbindung erfolgreich hergestellt wurde, beginnt daher die Methode ngx_http_init_connection mit der Verarbeitung der Anforderungsdaten für die Verbindung.

HTTP-Anfragenachricht empfangen

Vor dem Empfang der

HTTP-Anfrage wird zunächst die erfolgreich aufgebaute Verbindung ngx_http_init_connection initialisiert Die Funktion der Funktion besteht darin, die Rückrufmethode für Lese- und Schreibereignisse festzulegen. Tatsächlich führt die Rückrufmethode für Leseereignisse keine Operation aus 🎜>Anfrageprozess.

ngx_http_init_connection

Ablauf der Funktionsausführung: Legen Sie die Rückrufmethode zum Schreiben von Ereignissen auf der aktuellen Verbindung fest

Handler
    zu
  • ngx_http_empty_handler (eigentlich führt diese Methode keine Operation aus); Setzen Sie die Rückrufmethode des aktuellen Verbindungsleseereignisses handler
  • auf
  • ngx_http_wait_request_handler ;Überprüfen Sie, ob das Leseereignis auf der aktuellen Verbindung bereit ist (d. h. das Flag bereit
  • ist 1):
  • Wenn das Leseereignis-ready
      -Flag 1 ist, was anzeigt, dass auf der aktuellen Verbindung ein lesbarer
    • TCP-Stream vorhanden ist, wird die Rückrufmethode des Leseereignisses ausgeführt . ngx_http_wait_request_handler; Wenn das Leseereignis-ready
    • -Flag 0 ist, was darauf hinweist, dass auf der aktuellen Verbindung kein lesbarer
    • TCP-Stream vorhanden ist, das Ereignis wird gelesen Fügen Sie es dem Timer-Ereignismechanismus hinzu (überwachen Sie, ob das lesbare Ereignis abläuft) und registrieren Sie das gelesene Ereignis im epoll-Ereignismechanismus und warten Sie auf das Eintreten des lesbaren Ereignisses >Funktion
    • ngx_http_init_connection
    ist in der Datei
  • src/http/ngx_http_request.c
wie folgt definiert:

Wenn die Verbindung ist zum ersten Mal lesbar. Wenn ein Ereignis auftritt, wird die Funktion ngx_http_wait_request_handler aufgerufen. Die Funktion dieser Funktion besteht darin, die

HTTP
void
ngx_http_init_connection(ngx_connection_t *c)
{
    ngx_uint_t              i;
    ngx_event_t            *rev;
    struct sockaddr_in     *sin;
    ngx_http_port_t        *port;
    ngx_http_in_addr_t     *addr;
    ngx_http_log_ctx_t     *ctx;
    ngx_http_connection_t  *hc;
#if (NGX_HAVE_INET6)
    struct sockaddr_in6    *sin6;
    ngx_http_in6_addr_t    *addr6;
#endif

    /* 分配http连接ngx_http_connection_t结构体空间 */
    hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));
    if (hc == NULL) {
        ngx_http_close_connection(c);
        return;
    }

    c->data = hc;

    /* find the server configuration for the address:port */

    port = c->listening->servers;

    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
         */

        if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) {
            ngx_http_close_connection(c);
            return;
        }

        switch (c->local_sockaddr->sa_family) {

#if (NGX_HAVE_INET6)
        ...
#endif

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

            addr = port->addrs;

            /* the last address is "*" */

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

            hc->addr_conf = &addr[i].conf;

            break;
        }

    } else {

        switch (c->local_sockaddr->sa_family) {

#if (NGX_HAVE_INET6)
        ...
#endif

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

    /* the default server configuration for the address:port */
    hc->conf_ctx = hc->addr_conf->default_server->ctx;

    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 = "waiting for request";

    c->log_error = NGX_ERROR_INFO;

    /* 设置当前连接读、写事件的handler处理方法 */
    rev = c->read;
    /* 设置当前连接读事件的处理方法handler为ngx_http_wait_request_handler */
    rev->handler = ngx_http_wait_request_handler;
    /*
     * 设置当前连接写事件的处理方法handler为ngx_http_empty_handler,
     * 该方法不执行任何实际操作,只记录日志;
     * 因为处理请求的过程不需要write方法;
     */
    c->write->handler = ngx_http_empty_handler;

#if (NGX_HTTP_SPDY)
   ...
#endif

#if (NGX_HTTP_SSL)
    ...
#endif

    if (hc->addr_conf->proxy_protocol) {
        hc->proxy_protocol = 1;
        c->log->action = "reading PROXY protocol";
    }

    /* 若读事件准备就绪,则判断是否使用同步锁,
     * 根据同步锁情况判断决定是否立即处理该事件;
     */
    if (rev->ready) {
        /* the deferred accept(), rtsig, aio, iocp */

        /*
         * 若使用了同步锁ngx_use_accept_mutex,
         * 则将该读事件添加到待处理事件队列ngx_post_event中,
         * 直到退出锁时,才处理该读事件;
         */
        if (ngx_use_accept_mutex) {
            ngx_post_event(rev, &ngx_posted_events);
            return;
        }

        /* 若没有使用同步锁,则直接处理该读事件;
         * 读事件的处理函数handler为ngx_http_wait_request_handler;
         */
        rev->handler(rev);
        return;
    }

    /*
     * 若当前连接的读事件未准备就绪,
     * 则将其添加到定时器事件机制,并注册到epoll事件机制中;
     */

    /* 将当前连接的读事件添加到定时器机制中 */
    ngx_add_timer(rev, c->listening->post_accept_timeout);
    ngx_reusable_connection(c, 1);

    /* 将当前连接的读事件注册到epoll事件机制中 */
    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
        ngx_http_close_connection(c);
        return;
    }
}

-Anfrage zu initialisieren Die Anforderung wird unmittelbar nach erfolgreichem Verbindungsaufbau initialisiert. Die eigentliche Initialisierungsarbeit wird erst ausgeführt, wenn der Empfang der tatsächlichen Anforderungsdaten vom Client im entsprechenden Socket-Puffer bestätigt wird . Dies kann unnötigen Speicherverbrauch reduzieren (wenn der Client nach erfolgreichem Verbindungsaufbau nicht mit tatsächlichen Daten kommuniziert, reserviert

Nginx zu diesem Zeitpunkt Speicher aufgrund von Initialisierungsarbeiten). ngx_http_wait_request_handler Ablauf der Funktionsausführung:

  • 首先判断当前读事件是否超时(即读事件的 timedout 标志位是否为1):
    • timedout 标志位为1,表示当前读事件已经超时,则调用ngx_http_close_connection 方法关闭当前连接,return 从当前函数返回;
    • timedout 标志位为0,表示当前读事件还未超时,则继续检查当前连接的close标志位;
  • 若当前连接的 close 标志位为1,表示当前连接要关闭,则调用ngx_http_close_connection 方法关闭当前连接,return 从当前函数返回;
  • 若当前连接的 close 标志位为0,表示不需要关闭当前连接,进而调用recv() 函数尝试从当前连接所对应的套接字缓冲区中接收数据,这个步骤是为了确定客户端是否真正的发送请求数据,以免因为客户端不发送实际请求数据,出现初始化请求而导致内存被消耗。根据所读取的数据情况n 来判断是否要真正进行初始化请求工作:
    • n = NGX_AGAIN,表示客户端发起连接请求,但是暂时还没发送实际的数据,则将当前连接上的读事件添加到定时器机制中,同时将读事件注册到epoll 事件机制中,return 从当前函数返回;
    • n = NGX_ERROR,表示当前连接出错,则直接调用ngx_http_close_connection 关闭当前连接,return 从当前函数返回;
    • n = 0,表示客户端已经主动关闭当前连接,所有服务器端调用ngx_http_close_connection 关闭当前连接,return 从当前函数返回;
    • n 大于 0,表示读取到实际的请求数据,因此决定开始初始化当前请求,继续往下执行;
  • 调用 ngx_http_create_request 方法构造ngx_http_request_t 请求结构体,并设置到当前连接的data 成员;
  • 设置当前读事件的回调方法为 ngx_http_process_request_line,并执行该回调方法开始接收并解析请求行;

函数 ngx_http_wait_request_handler 在文件src/http/ngx_http_request.c 中定义如下:

/* 处理连接的可读事件 */
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;

    /* 获取读事件所对应的连接ngx_connection_t 对象 */
    c = rev->data;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http wait request handler");

    /* 若当前读事件超时,则记录错误日志,关闭所对应的连接并退出 */
    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;
    }

    /* 若当前读事件不超时,且其所对应的连接不设置close标志位,则继续指向以下语句 */

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

    /* 在当前连接上开始接收HTTP请求数据 */
    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;
    }

    /* 若接收HTTP请求数据成功,则调整接收缓冲区成员指针 */
    b->last += n;

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

        p = ngx_proxy_protocol_parse(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_t */
    c->data = ngx_http_create_request(c);
    if (c->data == NULL) {
        ngx_http_close_connection(c);
        return;
    }

    /* 设置当前读事件的处理方法为ngx_http_process_request_line */
    rev->handler = ngx_http_process_request_line;
    /* 执行该读事件的处理方法ngx_http_process_request_line,接收HTTP请求行 */
    ngx_http_process_request_line(rev);
}

接收 HTTP 请求行

        HTTP 请求的初始化完成之后会调用 ngx_http_process_request_line 方法开始接收并解析 HTTP 请求行。在 HTTP 协议中我们可以知道,请求行的长度并不是固定的,它与URI 长度相关,若当内核套接字缓冲区不能一次性完整的接收 HTTP 请求行时,会多次调用 ngx_http_process_request_line 方法继续接收,即ngx_http_process_request_line 方法重新作为当前连接上读事件的回调方法,必要时将读事件添加到定时器机制,注册到epoll 事件机制,直到接收并解析出完整的 HTTP 请求行。

ngx_http_process_request_line 处理HTTP 请求行函数执行流程:

  • 首先,判断当前请求是否超时,若超时(即读事件的 timedout 标志位为1),则设置当前连接的超时标志位为 1c->timedout = 1),调用 ngx_http_close_request 方法关闭该请求,并 return 从当前函数返回;
  • 若当前请求未超时(读事件的 timedout 标志位为 0),调用 ngx_http_read_request_header 方法开始读取当前请求行,根据该函数的返回值n 进行以下判断:
    • 若返回值 n = NGX_AGAIN,表示当前连接上套接字缓冲区不存在可读TCP 流,则需将当前读事件添加到定时器机制,注册到 epoll 事件机制中,等待可读事件发生。return 从当前函数返回;
    • 若返回值 n = NGX_ERROR,表示当前连接出错,则调用ngx_http_finalize_request 方法结束请求,return 从当前函数返回;
    • 若返回值 n 大于 0,表示读取请求行成功,调用函数 ngx_http_parse_request_line 开始解析由函数ngx_http_read_request_header 读取所返回的请求行,根据函数ngx_http_parse_request_line 函数返回值rc 不同进行判断;
  • 若返回值 rc = NGX_ERROR,表示解析请求行时出错,此时,调用ngx_http_finalize_request 方法终止该请求,并return 从当前函数返回;
  • 若返回值 rc = NGX_AGAIN,表示没有解析到完整的请求行,即仍需接收请求行,首先根据要求调整接收缓冲区header_in 的内存空间,则继续调用函数ngx_http_read_request_header 读取请求数据进入请求行自动处理机制,直到请求行解析完毕;
  • 若返回值 rc = NGX_OK,表示解析到完整的 HTTP 请求行,则设置请求行的成员信息(例如:方法名称、URI 参数、HTTP 版本等信息); 
    •  若 HTTP 协议版本小于 1.0 版本,表示不需要处理 HTTP 请求头部,则直接调用函数ngx_http_process_request 处理该请求,return 从当前函数返回;
    •  若HTTP协议版本不小于 1.0 版本,表示需要处理HTTP请求头部:
      •  调用函数 ngx_list_init 初始化保存 HTTP 请求头部的结构体 ngx_http_request_t 中成员headers_in 链表容器(该链表缓冲区是保存所接收到的 HTTP 请求数据); 
      • 设置当前读事件的回调方法为 ngx_http_process_request_headers 方法,并调用该方法ngx_http_process_request_headers 开始处理HTTP 请求头部。return 从当前函数返回;

函数 ngx_http_process_request_line 在文件src/http/ngx_http_request.c 中定义如下:

/* 处理HTTP请求行 */
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;
    }

    /* 设置NGX_AGAIN标志,表示请求行还没解析完毕 */
    rc = NGX_AGAIN;

    for ( ;; ) {

        /* 若请求行还没解析完毕,则继续解析 */
        if (rc == NGX_AGAIN) {
            /* 读取当前请求未解析的数据 */
            n = ngx_http_read_request_header(r);

            /* 若没有数据,或读取失败,则直接退出 */
            if (n == NGX_AGAIN || n == NGX_ERROR) {
                return;
            }
        }

        /* 解析接收缓冲区header_in中的请求行 */
        rc = ngx_http_parse_request_line(r, r->header_in);

        /* 若请求行解析完毕 */
        if (rc == NGX_OK) {

            /* the request line has been parsed successfully */

            /* 设置请求行的成员,请求行是ngx_str_t类型 */
            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;

            /* 设置HTTP请求协议 */
            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.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) {

                if (r->headers_in.server.len == 0
                    && ngx_http_set_virtual_server(r, &r->headers_in.server)
                       == NGX_ERROR)
                {
                    return;
                }

                /* 若HTTP版本小于1.0版本,则表示不需要接收HTTP请求头部,则直接处理请求 */
                ngx_http_process_request(r);
                return;
            }


            /* 初始化链表容器,为接收HTTP请求头部做准备 */
            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;
            /* 开始处理HTTP请求头部 */
            ngx_http_process_request_headers(rev);

            return;
        }

        /* 解析请求行出错 */
        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_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;
            }
        }
    }
}

        在接收并解析请求行的过程中会调用 ngx_http_read_request_header 读取请求数据,我们看下该函数是如何读取到请求数据的。

ngx_http_read_request_header 读取请求数据函数执行流程:

  • 检测当前请求的接收缓冲区 header_in 是否有数据,若有直接返回该数据n
  • 若接收缓冲区 header_in 没有数据,检查当前读事件是否准备就绪(即判断ready 标志位是否为 0 ):
    • 若当前读事件未准备就绪(即当前读事件 ready 标志位为0),则设置返回值 n= NGX_AGAIN
    • 若当前读事件已经准备就绪(即 ready 标志位为 1),则调用 recv() 方法从当前连接套接字中读取数据并保存到接收缓冲区header_in 中,并设置 nrecv() 方法所读取的数据的返回值;
  • 下面根据 n 的取值执行不同的操作:
    • n = NGX_AGAIN(此时,n 的值可能当前事件未准备就绪而设置的NGX_AGAIN,也可能是 recv() 方法返回的 NGX_AGAIN 值,但是只能是其中一种情况),将当前读事件添加到定时器事件机制中, 将当前读事件注册到epoll 事件机制中,等待事件可读,n 从当前函数返回;
    • n = 0n = ERROR,则调用 ngx_http_finalize_request 结束请求,并返回NGX_ERROR 退出当前函数;

函数 ngx_http_read_request_header 在文件src/http/ngx_http_request.c 中定义如下:

static ssize_t
ngx_http_read_request_header(ngx_http_request_t *r)
{
    ssize_t                    n;
    ngx_event_t               *rev;
    ngx_connection_t          *c;
    ngx_http_core_srv_conf_t  *cscf;

    /* 获取当前请求所对应的连接 */
    c = r->connection;
    /* 获取当前连接的读事件 */
    rev = c->read;

    /* 获取当前请求接收缓冲区的数据,header_in 是ngx_buf_t类型 */
    n = r->header_in->last - r->header_in->pos;

    /* 若接收缓冲区有数据,则直接返回该数据 */
    if (n > 0) {
        return n;
    }

    /* 若当前接收缓冲区没有数据,首先判断当前读事件是否准备就绪 */
    if (rev->ready) {
        /* 若当前读事件已准备就绪,则从其所对应的连接套接字读取数据,并保存到接收缓冲区中 */
        n = c->recv(c, r->header_in->last,
                    r->header_in->end - r->header_in->last);
    } else {
        /* 若接收缓冲区没有数据,且读事件未准备就绪,则设置为NGX_AGAIN */
        n = NGX_AGAIN;
    }

    /* 若接收缓冲区没有数据,且读事件未准备就绪,则设置为NGX_AGAIN */
    /* 将当前读事件添加到定时器机制;
     * 将当前读事件注册到epoll事件机制;
     */
    if (n == NGX_AGAIN) {
        if (!rev->timer_set) {
            cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
            /* 将当前读事件添加到定时器机制中 */
            ngx_add_timer(rev, cscf->client_header_timeout);
        }

        /* 将当前读事件注册到epoll事件机制中 */
        if (ngx_handle_read_event(rev, 0) != NGX_OK) {
            ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
            return NGX_ERROR;
        }

        return NGX_AGAIN;
    }

    if (n == 0) {
        ngx_log_error(NGX_LOG_INFO, c->log, 0,
                      "client prematurely closed connection");
    }

    if (n == 0 || n == NGX_ERROR) {
        c->error = 1;
        c->log->action = "reading client request headers";

        ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
        return NGX_ERROR;
    }

    r->header_in->last += n;

    return n;
}

接收 HTTP 请求头部

        前面已经成功接收并解析了 HTTP 请求行,这里根据读事件的回调方法ngx_http_process_request_headers 开始接收并解析HTTP 请求头部,但是并不一定能够一次性接收到完整的 HTTP 请求头部,因此,可以多次调用该函数,直到接收到完整的 HTTP 请求头部。

ngx_http_process_request_headers 处理HTTP 请求头部函数执行流程:

  • 首先,判断当前请求读事件是否超时,若超时(即读事件的 timedout 标志位为1),则设置当前连接超时标志位为 1c->timedout = 1),并调用 ngx_http_close_request 方法关闭该请求,并 return 从当前函数返回;
  • 若当前请求读事件未超时(即读事件的 timedout 标志位为0),检查接收 HTTP 请求头部的 header_in 缓冲区是否有剩余内存空间,若没有剩余的内存空间,则调用ngx_http_alloc_large_header_buffer 方法分配更大的缓冲区。若有剩余的内存,则无需再分配内存空间。
  • 调用 ngx_http_read_request_header 方法开始读取当前请求头部保存到header_in 缓冲区中,根据该函数的返回值 n 进行以下判断:
    • 若返回值 n = NGX_AGAIN,表示当前连接上套接字缓冲区不存在可读TCP 流,则需将当前读事件添加到定时器机制,注册到 epoll 事件机制中,等待可读事件发生。return 从当前函数返回;
    • 若返回值 n = NGX_ERROR,表示当前连接出错,则调用ngx_http_finalize_request 方法结束请求,return 从当前函数返回;
    • 若返回值 n 大于 0,表示读取请求头部成功,调用函数 ngx_http_parse_request_line 开始解析由函数ngx_http_read_request_header 读取所返回的请求头部,根据函数ngx_http_parse_request_line 函数返回值rc不同进行判断;
  • 若返回值 rc = NGX_ERROR,表示解析请求行时出错,此时,调用ngx_http_finalize_request 方法终止该请求,并return 从当前函数返回;
  • 若返回值 rc = NGX_AGAIN,表示没有解析到完整一行的请求头部,仍需继续接收TCP 字符流才能够是完整一行的请求头部,则 continue 继续调用函数ngx_http_read_request_headerngx_http_parse_request_line 方法读取并解析下一行请求头部,直到全部请求头部解析完毕;
  • 若返回值 rc = NGX_OK,表示解析出一行 HTTP 请求头部(注意:一行请求头部只是整个请求头部的一部分),判断当前解析出来的一行请求头部是否合法,若非法,则忽略当前一行请求头部,继续读取并解析下一行请求头部。若合法,则调用ngx_list_push 方法将该行请求头部设置到当前请求 ngx_http_request_t 结构体 header_in 缓冲区成员的headers 链表中,设置请求头部名称的 hash 值,并 continue 继续调用函数 ngx_http_read_request_headerngx_http_parse_request_line 方法读取并解析下一行请求头部,直到全部请求头部解析完毕;
  • 若返回值 rc = NGX_HTTP_PARSE_HEADER_DONE,则表示已经读取并解析出全部请求头部,此时,调用ngx_http_process_request 方法开始处理请求,return 从当前函数返回;

函数 ngx_http_process_request_headers 在文件src/http/ngx_http_request.c 中定义如下:

/* 处理HTTP请求头部 */
static void
ngx_http_process_request_headers(ngx_event_t *rev)
{
    u_char                     *p;
    size_t                      len;
    ssize_t                     n;
    ngx_int_t                   rc, rv;
    ngx_table_elt_t            *h;
    ngx_connection_t           *c;
    ngx_http_header_t          *hh;
    ngx_http_request_t         *r;
    ngx_http_core_srv_conf_t   *cscf;
    ngx_http_core_main_conf_t  *cmcf;

    /* 获取当前读事件所对应的连接 */
    c = rev->data;
    /* 获取当前连接的HTTP请求 */
    r = c->data;

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

    /* 获取ngx_http_core_module模块的main级别配置项结构 */
    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);

                if (rv == NGX_ERROR) {
                    ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
                    return;
                }

                if (rv == NGX_DECLINED) {
                    p = r->header_name_start;

                    r->lingering_close = 1;

                    if (p == NULL) {
                        ngx_log_error(NGX_LOG_INFO, c->log, 0,
                                      "client sent too large request");
                        ngx_http_finalize_request(r,
                                            NGX_HTTP_REQUEST_HEADER_TOO_LARGE);
                        return;
                    }

                    len = r->header_in->end - p;

                    if (len > NGX_MAX_ERROR_STR - 300) {
                        len = NGX_MAX_ERROR_STR - 300;
                        p[len++] = '.'; p[len++] = '.'; p[len++] = '.';
                    }

                    ngx_log_error(NGX_LOG_INFO, c->log, 0,
                                  "client sent too long header line: \"%*s\"",
                                  len, r->header_name_start);

                    ngx_http_finalize_request(r,
                                            NGX_HTTP_REQUEST_HEADER_TOO_LARGE);
                    return;
                }
            }

            /* 读取未解析请求数据 */
            n = ngx_http_read_request_header(r);

            /* 若没有可读的数据,或读取失败,则直接退出 */
            if (n == NGX_AGAIN || n == NGX_ERROR) {
                return;
            }
        }

        /* the host header could change the server configuration context */

        /* 获取ngx_http_core_module模块的srv级别配置项结构 */
        cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);

        /* 开始解析HTTP请求头部 */
        rc = ngx_http_parse_header_line(r, r->header_in,
                                        cscf->underscores_in_headers);

        /* 解析出一行请求头部(注意:一行请求头部只是HTTP请求头部的一部分) */
        if (rc == NGX_OK) {

            /* 设置当前请求的长度 */
            r->request_length += r->header_in->pos - r->header_name_start;

            /*
             * 若当前解析出来的一行请求头部是非法的,或Nginx当前版本不支持,
             * 则记录错误日志,并继续解析下一行请求头部;
             */
            if (r->invalid_header && cscf->ignore_invalid_headers) {

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

                ngx_log_error(NGX_LOG_INFO, c->log, 0,
                              "client sent invalid header line: \"%*s\"",
                              r->header_end - r->header_name_start,
                              r->header_name_start);
                continue;
            }

            /* a header line has been parsed successfully */

            /*
             * 若当前解析出来的一行请求头部是合法的,表示成功解析出该行请求头部,
             * 将该行请求头部保存在当前请求的headers_in的headers链表中;
             * 接着继续解析下一行请求头部;
             */
            h = ngx_list_push(&r->headers_in.headers);
            if (h == NULL) {
                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
                return;
            }

            /* 设置请求头部名称的hash值 */
            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);
            }

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

            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                           "http header: \"%V: %V\"",
                           &h->key, &h->value);

            continue;
        }

        /* 若成功解析所有请求头部,则接下来就开始处理该请求 */
        if (rc == NGX_HTTP_PARSE_HEADER_DONE) {

            /* a whole header has been parsed successfully */

            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                           "http header done");

            r->request_length += r->header_in->pos - r->header_name_start;

            /* 设置当前请求的解析状态 */
            r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;

            /*
             * 调用该函数主要目的有两个:
             * 1、根据HTTP头部的host字段,调用ngx_http_find_virtual_server查找虚拟主机的配置块;
             * 2、对HTTP请求头部协议版本进行检查,例如http1.1版本,host头部不能为空,否则会返回400 Bad Request错误;
             */
            rc = ngx_http_process_request_header(r);

            if (rc != NGX_OK) {
                return;
            }

            /* 开始处理当前请求 */
            ngx_http_process_request(r);

            return;
        }

        /* 表示当前行的请求头部未解析完毕,则继续读取请求数据进行解析 */
        if (rc == NGX_AGAIN) {

            /* a header line parsing is still not complete */

            continue;
        }

        /* rc == NGX_HTTP_PARSE_INVALID_HEADER: "\r" is not followed by "\n" */

        /* 解析请求头部出错,则关闭该请求,并退出 */
        ngx_log_error(NGX_LOG_INFO, c->log, 0,
                      "client sent invalid header line: \"%*s\\r...\"",
                      r->header_end - r->header_name_start,
                      r->header_name_start);
        ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
        return;
    }
}

处理 HTTP 请求

        前面的步骤已经接收到完整的 HTTP 请求头部,此时,已经有足够的信息开始处理HTTP 请求。处理 HTTP 请求的过程有 11HTTP 阶段,在不同的阶段由各个 HTTP 模块进行处理。

ngx_http_process_request 处理HTTP 请求函数执行流程:

  • 若当前读事件在定时器机制中,则调用 ngx_del_timer 函数将其从定时器机制中移除,因为在处理HTTP 请求时不存在接收 HTTP 请求头部超时的问题;
  • 由于处理 HTTP 请求不需要再接收 HTTP 请求行或头部,则需重新设置当前连接读、写事件的回调方法,读、写事件的回调方法都设置为 ngx_http_request_handler,即后续处理 HTTP 请求的过程都是通过该方法进行;
  • 设置当前请求 ngx_http_request_t 结构体中的成员read_event_handler 的回调方法为 ngx_http_block_reading,该回调方法实际不做任何操作,即在处理请求时不会对请求的读事件进行任何处理,除非某个HTTP模块重新设置该回调方法;
  • 接下来调用函数 ngx_http_handler 开始处理HTTP 请求;
  • 调用函数 ngx_http_run_posted_requests 处理post 子请求;

函数 ngx_http_process_request 在文件src/http/ngx_http_request.c 中定义如下:

/* 处理HTTP请求 */
void
ngx_http_process_request(ngx_http_request_t *r)
{
    ngx_connection_t  *c;

    /* 获取当前请求所对应的连接 */
    c = r->connection;

#if (NGX_HTTP_SSL)
    ...

#endif

    /*
     * 由于现在不需要再接收HTTP请求头部超时问题,
     * 则需要把当前连接的读事件从定时器机制中删除;
     * timer_set为1表示读事件已添加到定时器机制中,
     * 则将其从定时器机制中删除,0表示不在定时器机制中;
     */
    if (c->read->timer_set) {
        ngx_del_timer(c->read);
    }

#if (NGX_STAT_STUB)
    ...
#endif

    /* 重新设置当前连接的读、写事件的回调方法 */
    c->read->handler = ngx_http_request_handler;
    c->write->handler = ngx_http_request_handler;

    /*
     * 设置请求读事件的回调方法,
     * 其实ngx_http_block_reading函数实际对读事件不做任何处理;
     * 即在处理请求时,不会对读事件任何操作,除非有HTTP模块重新设置处理方法;
     */
    r->read_event_handler = ngx_http_block_reading;

    /* 开始处理各个HTTP模块的handler方法,该函数定义于ngx_http_core_module.c中*/
    ngx_http_handler(r);

    /* 处理post请求 */
    ngx_http_run_posted_requests(c);
}

ngx_http_handler 函数的执行流程:

  • 检查当前请求 ngx_http_request_tinternal 标志位:
    • internal 标志位为 0,表示当前请求不需要重定向,判断是否使用 keepalive 机制,并设置phase_handler 序号为 0,表示执行 ngx_http_phase_engine_t 结构成员ngx_http_phase_handler_t *handlers数组中的第一个回调方法;
    • internal 标志位为 1,表示需要将当前请求做内部跳转,并将 phase_handler 设置为server_rewriter_index,表示执行 ngx_http_phase_engine_t 结构成员ngx_http_phase_handler_t *handlers 数组在NGX_HTTP_SERVER_REWRITE_PHASE 处理阶段的第一个回调方法;
  • 设置当前请求 ngx_http_request_t 的成员写事件write_event_handlerngx_http_core_run_phases
  • 执行n gx_http_core_run_phases 方法;

函数 ngx_http_handler 在文件 src/http/ngx_http_core_module.c 中定义如下:

void
ngx_http_handler(ngx_http_request_t *r)
{
    ngx_http_core_main_conf_t  *cmcf;

    r->connection->log->action = NULL;

    r->connection->unexpected_eof = 0;

    /* 若当前请求的internal标志位为0,表示不需要重定向 */
    if (!r->internal) {
        /* 下面语句是决定是否使用keepalive机制 */
        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->headers_in.chunked);
        /*
         * phase_handler序号设置为0,表示执行ngx_http_phase_engine_t结构体成员
         * ngx_http_phase_handler_t *handlers数组中的第一个回调方法;
         */
        r->phase_handler = 0;

    } else {
    /* 若当前请求的internal标志位为1,表示需要做内部跳转 */
        /* 获取ngx_http_core_module模块的main级别的配置项结构 */
        cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
        /*
         * 将phase_handler序号设为server_rewriter_index,
         * 该phase_handler序号是作为ngx_http_phase_engine_t结构中成员
         * ngx_http_phase_handler_t *handlers回调方法数组的序号,
         * 即表示回调方法在该数组中所处的位置;
         *
         * server_rewrite_index则是handlers数组中NGX_HTTP_SERVER_REWRITE_PHASE阶段的
         * 第一个ngx_http_phase_handler_t回调的方法;
         */
        r->phase_handler = cmcf->phase_engine.server_rewrite_index;
    }

    r->valid_location = 1;
#if (NGX_HTTP_GZIP)
    r->gzip_tested = 0;
    r->gzip_ok = 0;
    r->gzip_vary = 0;
#endif

    /* 设置当前请求写事件的回调方法 */
    r->write_event_handler = ngx_http_core_run_phases;
    /*
     * 执行该回调方法,将调用各个HTTP模块共同处理当前请求,
     * 各个HTTP模块按照11个HTTP阶段进行处理;
     */
    ngx_http_core_run_phases(r);
}

ngx_http_core_run_phases 函数的执行流程:

  • 判断每个 ngx_http_phase_handler_t 处理阶段是否实现checker 方法:
    • 若实现 checker 方法,则执行 phase_handler 序号在 ngx_http_phase_handler_t *handlers数组中指定的 checker 方法;执行完 checker 方法,若返回 NGX_OK 则退出;若返回非NGX_OK,则继续执行下一个 HTTP 模块在该阶段的 checker 方法;
    • 若没有实现 checker 方法,则直接退出;

函数 ngx_http_core_run_phases 在文件src/http/ngx_http_core_module.c 中定义如下:

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;

    /* 获取ngx_http_core_module模块的main级别的配置项结构体 */
    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

    /* 获取各个HTTP模块处理请求的回调方法数组 */
    ph = cmcf->phase_engine.handlers;

    /* 若实现了checker方法 */
    while (ph[r->phase_handler].checker) {

        /* 执行phase_handler序号在数组中指定的checker方法 */
        rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);

        /* 成功执行checker方法,则退出,否则继续执行下一个HTTP模块的checker方法 */
        if (rc == NGX_OK) {
            return;
        }
    }
}

处理子请求

        post 子请求是基于 subrequest 机制的,首先看下 post 子请求结构体类型:

/* 子请求的单链表结构 */
typedef struct ngx_http_posted_request_s  ngx_http_posted_request_t;

struct ngx_http_posted_request_s {
    /* 指向当前待处理子请求的ngx_http_request_t结构体 */
    ngx_http_request_t               *request;
    /* 指向下一个子请求 */
    ngx_http_posted_request_t        *next;
};

        在请求结构体 ngx_http_request_t 中有一个与post 子请求相关的成员 posted_requests,该成员把各个 post 子请求按照子请求结构体ngx_http_posted_request_t 的结构连接成单链表的形式,请求结构体ngx_http_request_tmain 成员是子请求的原始请求,parent 成员是子请求的父请求。下面是子请求的处理过程。

ngx_http_run_posted_requests 函数执行流程:

  • 判断当前连接是否已被销毁(即标志位 destroyed 是否为0),若被销毁则直接 return 退出,否则继续执行;
  • 获取原始请求的子请求链表,若子请求链表为空(表示没有 post 请求)则直接return 退出,否则继续执行;
  • 遍历子请求链表,执行每个 post 请求的写事件回调方法write_event_handler

函数 ngx_http_run_posted_requests 在文件src/http/ngx_http_request.c 中定义如下:

void
ngx_http_run_posted_requests(ngx_connection_t *c)
{
    ngx_http_request_t         *r;
    ngx_http_log_ctx_t         *ctx;
    ngx_http_posted_request_t  *pr;

    for ( ;; ) {

        /* 若当前连接已被销毁,则直接退出 */
        if (c->destroyed) {
            return;
        }

        /* 获取当前连接所对应的请求 */
        r = c->data;
        /* 获取原始请求的子请求单链表 */
        pr = r->main->posted_requests;

        /* 若子请求单链表为空,则直接退出 */
        if (pr == NULL) {
            return;
        }

        /* 将原始请求的posted_requests指向单链表的下一个post请求 */
        r->main->posted_requests = pr->next;

        /* 获取子请求链表中的第一个post请求 */
        r = pr->request;

        ctx = c->log->data;
        ctx->current_request = r;

        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
                       "http posted request: \"%V?%V\"", &r->uri, &r->args);

        /*
         * 调用当前post请求写事件的回调方法write_event_handler;
         * 子请求不被网络事件驱动,因此不需要调用read_event_handler;
         */
        r->write_event_handler(r);
    }
}

处理 HTTP 请求包体

        下面开始要分析 HTTP 框架是如何处理HTTP 请求包体,HTTP 框架有两种处理请求包体的方法:接收请求包体、丢弃请求包体;但是必须要注意的是丢弃请求包体并不意味着就不接受请求包体,只是把接收到的请求包体进行丢弃,不进一步对其进行处理。

        其中有一个很重要的成员就是请求结构体 ngx_http_request_t  中的引用计数count,引用计数是用来决定是否真正结束当前请求,若引用计数为0 时,表示没有其他动作在处理该请求,则可以终止该请求;若引用计数不为0 时,表示当前请求还有其他动作在操作,因此不能结束当前请求,以免发生错误;那怎么样控制这个引用计数呢?例如,当一个请求添加新事件,或是把一些原本从定时器、epoll 事件机制中移除的事件从新加入到其中等等,出现这些情况都是要对引用计数增加1;当要结束请求时,首先会把引用计数减 1,并判断该引用计数是否为 0,再进一步判断是否决定真的结束当前请求。

接收 HTTP 请求包体

        HTTP 请求包体保存在结构体 ngx_http_request_body_t 中,该结构体是存放在保存着请求结构体 ngx_http_request_t 的成员 request_body 中,该结构体定义如下:

/* 存储HTTP请求包体的结构体ngx_http_request_body_t */
typedef struct {
    /* 存放HTTP请求包体的临时文件 */
    ngx_temp_file_t                  *temp_file;
    /*
     * 指向接收HTTP请求包体的缓冲区链表表头,
     * 因为当一个缓冲区ngx_buf_t无法容纳所有包体时,就需要多个缓冲区形成链表;
     */
    ngx_chain_t                      *bufs;
    /* 指向当前保存HTTP请求包体的缓冲区 */
    ngx_buf_t                        *buf;
    /*
     * 根据content-length头部和已接收包体长度,计算还需接收的包体长度;
     * 即当前剩余的请求包体大小;
     */
    off_t                             rest;
    /* 接收HTTP请求包体缓冲区链表空闲缓冲区 */
    ngx_chain_t                      *free;
    /* 接收HTTP请求包体缓冲区链表已使用的缓冲区 */
    ngx_chain_t                      *busy;
    /* 保存chunked的解码状态,供ngx_http_parse_chunked方法使用 */
    ngx_http_chunked_t               *chunked;
    /*
     * HTTP请求包体接收完毕后执行的回调方法;
     * 即ngx_http_read_client_request_body方法传递的第 2 个参数;
     */
    ngx_http_client_body_handler_pt   post_handler;
} ngx_http_request_body_t;

接收 HTTP 请求包体 ngx_http_read_client_request_body 函数执行流程:

  • Ursprüngliche Anforderungsreferenzberechnungr->main->count erhöht1; die Verwaltung der Referenzanzahlcount ist: wenn die Logik ist eingeschaltet. Während des Prozesses wird der Referenzzähler um 1 erhöht, und wenn der Prozess endet, wird der Referenzzähler verringert. 1. Erhöhen Sie in der Funktion ngx_http_read_client_request_body zunächst den Referenzzähler der ursprünglichen Anfrage um 1. Wenn eine abnormale Beendigung auftritt, wird der Referenzzähler verringert, bevor die Funktion zurückkehrt. 1; Wenn es normal endet, wird der Referenzzähler weiterhin von der post_handler-Rückrufmethode verwaltet
  • Stellen Sie fest, ob der aktuelle Anforderungstext vollständig empfangen wurde (r-> ;request_body ist 1) oder wird verworfen (r->discard_body ist 1), wenn eine davon erfüllt ist, besteht keine Notwendigkeit, den Anforderungspakettext erneut zu empfangen, und die Rückrufmethode post_handler wird direkt ausgeführt, und NGX_OK wird von der aktuellen Funktion zurückgegeben.
  • Wenn Sie den HTTP -Anforderungstext erhalten müssen, rufen Sie zuerst die Methode ngx_http_test_expect auf, um zu überprüfen, ob Der Client sendet Expect:100-continue Der Header erwartet, dass der Anforderungstext gesendet wird, und der Server antwortet mit HTTP/1.1 100 Continue und gibt an, dass der Client die Anforderung senden darf body;
  • weist die aktuelle Anfrage zu ngx_http_request_t Struktur request_body Mitglied, bereit, den Inhalt des Anfragepakets zu empfangen;
  • Überprüfen Sie den Inhalt der Anfrage -length Header, wenn der Anforderungsheader Wenn das Feld content-length kleiner als 0 ist, bedeutet dies, dass kein weiterer Empfang des Anforderungspaketkörpers erforderlich ist (das (d. h. der vollständige Anforderungspakettext wurde empfangen) und die Rückrufmethode post_handler wird von der aktuellen Funktion zurückgegeben Wenn das Feld content-length
  • im Anforderungsheader größer als
  • 0 ist, bedeutet dies, dass weiterhin der Text des Anforderungspakets empfangen werden muss. Stellen Sie zunächst fest, ob das header_in-Mitglied der aktuellen Anfrage ngx_http_request_t unverarbeitete Daten enthält. Wenn unverarbeitete Daten vorhanden sind, bedeutet dies, dass der Puffer header_in die Anfrage empfängt Der Anfragetext wurde in diesem Zeitraum vorab empfangen, da es möglich ist, den Anfragetext während des Empfangs des HTTP -Anfrageheaders vorab zu empfangen Der Hauptteil wird empfangen, wenn sich unverarbeitete Daten im Puffer befinden, bei dem es sich um den Hauptteil des Anforderungspakets handelt. Wenn sich unverarbeitete Daten im header_in
      -Puffer befinden, der der vorab empfangene Anforderungspaketkörper ist, überprüfen Sie zunächst, ob die Länge des Pufferanforderungspakets
    • preread größer ist als die Länge des Anforderungskörpers content-length Feld, wenn es größer ist, bedeutet dies, dass es vollständig ist HTTP Pakettext anfordern, wenn kein weiterer Empfang erforderlich ist, führen Sie die Rückrufmethode post_handler aus; Wenn im header_in Puffer, d. h. der vorab empfangene Anforderungspaketkörper, aber die Länge des Pufferanforderungspaketkörpers
    • preread
    • ist kleiner als die Länge des Anforderungspaketkörpers Das Feld content-length zeigt an, dass der empfangene Anforderungspakethauptteil unvollständig ist und Sie den Anforderungspakethauptteil weiterhin empfangen müssen.Aufruffunktion ngx_http_request_body_filte Analysieren Sie den empfangenen Anforderungstext und hängen Sie ihn an die Anforderung an ngx_http_request_t r request_body->bufs, header_in Der verbleibende Platz im Puffer reicht aus, um die verbleibende Anforderungspaketgröße rest zu empfangen Dann ist es nicht erforderlich, einen neuen Puffer zuzuweisen und die aktuelle Anforderung festzulegen ngx_http_request_t Die Rückrufmethode für Leseereignisse von read_event_handler ist ngx_http_read_client_request_body_handler und die Rückrufmethode für Schreibereignisse write_event_handler ngx_http_request_empty_handler (d. h. keine Operation ausführen) und rufen Sie dann die Methode ngx_http_do_read_client_request_body auf, um tatsächlich den HTTP -Anfragetext zu empfangen, der dies tun wird TCP Lesen Sie alle Zeichenströme im Socket-Puffer der Verbindung und bestimmen Sie, ob sie in eine temporäre Datei geschrieben werden müssen und ob alle Anforderungspaketkörper gleichzeitig nach dem Empfang empfangen werden sollen der komplette Paketkörper nach Ausführung der Callback-Methode post_handler;
    • Wenn sich im Puffer header_in unverarbeitete Daten befinden, handelt es sich um einen vorab empfangenen Anforderungspaketkörper, die Länge des Pufferanforderungspaketkörpers beträgt jedoch preread ist kleiner als die Länge des Anforderungspaketkörpers content-length-Feld oder es gibt keine unverarbeiteten Daten im header_in-Puffer und der verbleibende Speicherplatz von header_in reicht nicht zum Empfangen aus HTTP fordert den Paketkörper an, der Puffer zum Empfangen des Anforderungspaketkörpers wird neu zugewiesen und dann wird die aktuelle Anforderung festgelegt Die Rückrufmethode für Leseereignisse von ngx_http_request_t ist ngx_http_read_client_request_body_handler, und die Rückrufmethode für Schreibereignisse write_event_handler ist ngx_http_request_empty_hand ler (d. h. keine Operation ausführen). ) und rufen Sie dann die Methode auf ngx_http_do_read_client_request_body Wirklich angekommen HTTP Anfragetext;
  • Funktion
ngx_http_read_client_request_body

in der Datei src/http/ngx_http_request_body.c Die Die Definition lautet wie folgt: /* HTTP-Anfragetext empfangen*/ ngx_int_t ngx_http_read_client_request_body(ngx_http_request_t *r, ngx_http_client_body_handler_pt post_handler) { size_t preread; ssize_t Größe; ngx_int_t rc; ngx_buf_t *b; ngx_chain_t out, *cl; ngx_http_request_body_t *rb; ngx_http_core_loc_conf_t *clcf; /* * Wenn eine Logik zum Starten des Prozesses vorhanden ist, erhöht sich der Referenzzähler um 1. Wenn der Prozess endet, verringert sich der Referenzzähler um 1; * Erhöhen Sie in der Methode ngx_http_read_client_request_body zunächst den ursprünglichen Anforderungsreferenzzähler um 1. * Wenn eine abnormale Beendigung auftritt, wird der Referenzzähler um 1 verringert, bevor die Funktion zurückkehrt. * Wenn es normal endet, wird der Referenzzähler weiterhin von der post_handler-Methode verwaltet. */ /* Erhöhen Sie die Referenzanzahl der ursprünglichen Anfrage um 1 */ r->main->count ; #if (NGX_HTTP_SPDY) if (r->spdy_stream && r == r->main) { rc = ngx_http_spdy_read_request_body(r, post_handler); goto done; } #endif /* Wenn der HTTP-Anforderungstext nicht verarbeitet wird, wird die request_body-Struktur nicht zugewiesen und wird erst zugewiesen, wenn sie verarbeitet wird */ /* * Wenn es sich bei der aktuellen HTTP-Anfrage nicht um eine Originalanfrage handelt oder der HTTP-Anfragetext gelesen oder verworfen wurde; * Führen Sie dann direkt die Rückrufmethode post_handler des HTTP-Moduls aus und geben Sie NGX_OK zurück. */ if (r != r->main || r->request_body || r->discard_body) { post_handler(r); return NGX_OK; } /* * ngx_http_test_expect wird verwendet, um zu überprüfen, ob der Client den Expect:100-continue-Header sendet. * Wenn der Client diesen Header gesendet hat, um anzuzeigen, dass er erwartet, die Daten des Anforderungshauptteils zu senden, wird der Server zurückkehren

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn