搜尋
首頁後端開發php教程Nginx 中處理 HTTP 請求

概述

        在Nginx 的初始化啟動過程中,worker 工作進程會呼叫事件模組的ngx_event_process_init_proclist 方法為每個監聽套件 連接,並設定該連接上讀取事件的回調方法 handlerngx_event_accept,同時將讀取事件掛載到epoll 事件機制中等待監聽套接字連接上的可讀事件發生,到此,Nginx 就可以接收來自客戶端的請求。當監聽套接字連線上的可讀事件發生時,也就是該連線上有來自客戶端發出的連線要求,則會啟動讀取事件的handler 回呼方法ngx_event_accept,在ngx_event_accept 方法中呼叫 accept() 函式接收來自客戶端的連線請求,成功建立連線之後,ngx_event_accept 函式呼叫監聽套接字上的handler 回呼方法 ls->handler(c)(此回呼方法就是ngx_http_init_connection)。因此,成功建立連線之後由ngx_http_init_connection 方法開始處理該連線上的請求資料。 接收HTTP 請求訊息

        在接收HTTP 請求之前,首先會初始化已成功建立的連接;

ngx_http_init_connection

函數的功能是讀回回呼方法並沒有進行任何操作,讀取事件的回呼方法是對HTTP 請求進程初始化工作。 ngx_http_init_connection

函數的執行流程:

設定當前連接上寫事件的回呼方法handler

    ngx_http.的回呼方法
  • handlerngx_http_wait_request_handler
  • 檢查目前連接上讀取事件是否準備就緒(即
  • ready 標誌位元為1): 若讀事件 ready
  • 標誌位元為1,表示目前連線上有可讀的
  • TCP 流,則執行讀取事件的回呼方法 ngx_http_wait_request_handler
    • 若讀事件ready 標誌位元為0,表示目前連接上沒有可讀取的TCP 串流,則將讀取事件加入定時事件機制中(監控是否可讀事件機制(監控事件是否為超時)同時將讀事件註冊到epoll
    • 事件機制中,等待可讀事件的發生;
    • 函數ngx_http_init_connection 在文件
    • src/http/ngx_http_request.c
    中定義如下:
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 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;
    }
}

        當連接上第一次可讀事件時,會呼叫

ngx_http_wait_request_handler 函數,該函數的功能是初始化HTTP 請求,但是它並不會在成功建立連線之後就立刻初始化請求,而是在目前連線所對應的在套接字緩衝區上確定接收到來自客戶端的實際請求資料時才真正進行初始化工作,這樣做可以減少不必要的記憶體消耗(若當成功建立連接之後,客戶端並不進行實際資料通信,而此時Nginx

卻因為初始化工作分配記憶體)。

ngx_http_wait_request_handler 函數的執行流程:

  • 首先判断当前读事件是否超时(即读事件的 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 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 函数执行流程:

  • 原始請求引用計算r->main->count 增加1;引用計數count 的管理是:當邏輯開啟流程時,引用計數就會增加1,結束此流程時,引用計數就減 1。在ngx_http_read_client_request_body 函數中,首先將原始請求的參考計數增加1,當遇到異常終止時,引用計數會在該函數返回之前減 1;若正常結束時,引用計數由post_handler回呼方法繼續維護;
  • 判斷目前請求包體是否已完整接收(r->request_body1)或丟棄(r-> discard_body1),若滿足其中一個則不需要再次接收請求包體,直接執行post_handler 回調方法,並 NGX_OK 從目前函數回傳;
  • 若需要接收HTTP 請求包體,首先呼叫ngx_http_test_expect 方法,檢查客戶端是否發送Expect:100-continue回覆HTTP/1.1 100 Continue 表示允許客戶端發送請求包體;
  • 分配當前請求
  • ngx_http_request_t 結構體request_body 成員,準備接收請求包體; ,若請求頭部的 content-length
  • 欄位小於
  • 0,則表示不需要繼續接收請求包體(即已經接收到完整的請求包體),直接執行post_handler 回調方法,並NGX_OKpost_handler 回調方法,並NGX_OK從目前函數傳回;若要求頭部的
  • content-length
  • 欄位大於0,則表示需要繼續接收請求包體。首先判斷目前請求ngx_http_request_theader_in 成員是否存在未處理數據,若存在未被處理的數據,表示該緩衝區header_in 在接收請求頭部期間已經預先接收了請求包體,因為在接收HTTP 請求頭部期間有可能預先接收請求包體,由於在接收請求包體之前,請求頭部已經被接收完畢,所以若該緩衝區存在未被處理的數據,那就是請求包體。
      header_in
    • 緩衝區存在未被處理的數據,即是預先接收的請求包體,首先檢查緩衝區請求包體長度preread 是否大於請求包體長度的 content-length 字段,若大於則表示已接收完整的 HTTP 請求包體,不需要繼續接收,則執行post_handler 回調方法;
    • header_in
    • 緩衝區存在未被處理的資料,即是預先接收的請求包體,但是緩衝區請求包體長度preread 小於請求包體長度的 content-length 字段,表示已收到的請求包體不完整,則需要繼續接收請求包體。呼叫函數 ngx_http_request_body_filte 解析並把已接收的請求包體掛載到請求 ngx_http_request_t r 的 request_body->bufsheader_in 緩衝區剩餘的空間足以接收剩餘的請求包體大小rest,則不需要分配新的緩衝區,進而設置當前請求 ngx_http_request_t  的 read_event_handler 讀取事件回呼方法為ngx_http_read_client_request_body_handler,寫事件write_event_handler 回調方法為 ngx_http_request_empty_handler (即不執行任何操作),然後呼叫方法ngx_http_do_read_client_request_body 真正接收HTTP 包體,該方法將請求包體,該方法將請求 TCP 連接上的套接字緩衝區中的字元流全部讀取出來,並判斷是否需要寫入到臨時文件,以及是否接收全部的請求包體,同時在接收到完整包體後執行回調方法 post_handler
    • header_in 緩衝區存在未被處理的數據,即是預先接收的請求包體,但是緩衝區請求包體長度preread 小於請求包體長度的 content-length 字段,或 header_in 緩衝區不存在未被處理的數據,且header_in 剩餘的空間不足夠接收 HTTP 請求包體,則會重新分配接收請求包體的緩衝區,再進而設定目前請求 ngx_http_request_t read_event_handler 讀事件回調方法為ngx_http_read_client_request_body_handler,寫事件write_event_handler 回調方法為 ngx_http_request_empty_handler (即不執行任何操作),然後調用方法 ngx_http_do_read_client_request_body 真正接收 HTTP 請求包體;

函數 ngx_http_read_client_request_body 在檔案src/http/ngx_http_request_body.c 在檔案

src/http/ngx_http_request_body.c
 在檔案🎜src/http/ngx_http_request_body.c🎜 程式包中🎜*
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 size;
    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;

    /*
     * 當有邏輯開啟流程時,引用計數會增加1,此流程結束時,引用計數將減1;
     * 在ngx_http_read_client_request_body方法中,首先將原始請求參考計數增加1,
     * 當遇到異常終止時,則在該函數返回前會將引用計數減1,;
     * 若正常結束時,引用計數由post_handler方法繼續維護;
     */
    /* 原始請求的引用計數count加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

    /* HTTP請求包體未被處理時,request_body結構是不被分配的,只有處理時才會分配 */
    /*
     * 若目前HTTP請求不是原始請求,或HTTP請求包體已被讀取或被丟棄;
     * 則直接執行HTTP模組的回呼方法post_handler,並傳回NGX_OK;
     */
    if (r != r->main || r->request_body || r->discard_body) {
        post_handler(r);
        return NGX_OK;
    }

    /*
     * ngx_http_test_expect 用於檢查客戶端是否發送Expect:100-continue頭部,
     * 若用戶端已發送該頭部表示期望發送請求包體數據,則伺服器回

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
PHP:服務器端腳本語言的簡介PHP:服務器端腳本語言的簡介Apr 16, 2025 am 12:18 AM

PHP是一種服務器端腳本語言,用於動態網頁開發和服務器端應用程序。 1.PHP是一種解釋型語言,無需編譯,適合快速開發。 2.PHP代碼嵌入HTML中,易於網頁開發。 3.PHP處理服務器端邏輯,生成HTML輸出,支持用戶交互和數據處理。 4.PHP可與數據庫交互,處理表單提交,執行服務器端任務。

PHP和網絡:探索其長期影響PHP和網絡:探索其長期影響Apr 16, 2025 am 12:17 AM

PHP在過去幾十年中塑造了網絡,並將繼續在Web開發中扮演重要角色。 1)PHP起源於1994年,因其易用性和與MySQL的無縫集成成為開發者首選。 2)其核心功能包括生成動態內容和與數據庫的集成,使得網站能夠實時更新和個性化展示。 3)PHP的廣泛應用和生態系統推動了其長期影響,但也面臨版本更新和安全性挑戰。 4)近年來的性能改進,如PHP7的發布,使其能與現代語言競爭。 5)未來,PHP需應對容器化、微服務等新挑戰,但其靈活性和活躍社區使其具備適應能力。

為什麼要使用PHP?解釋的優點和好處為什麼要使用PHP?解釋的優點和好處Apr 16, 2025 am 12:16 AM

PHP的核心優勢包括易於學習、強大的web開發支持、豐富的庫和框架、高性能和可擴展性、跨平台兼容性以及成本效益高。 1)易於學習和使用,適合初學者;2)與web服務器集成好,支持多種數據庫;3)擁有如Laravel等強大框架;4)通過優化可實現高性能;5)支持多種操作系統;6)開源,降低開發成本。

揭穿神話:PHP真的是一種死語嗎?揭穿神話:PHP真的是一種死語嗎?Apr 16, 2025 am 12:15 AM

PHP沒有死。 1)PHP社區積極解決性能和安全問題,PHP7.x提升了性能。 2)PHP適合現代Web開發,廣泛用於大型網站。 3)PHP易學且服務器表現出色,但類型系統不如靜態語言嚴格。 4)PHP在內容管理和電商領域仍重要,生態系統不斷進化。 5)通過OPcache和APC等優化性能,使用OOP和設計模式提升代碼質量。

PHP與Python辯論:哪個更好?PHP與Python辯論:哪個更好?Apr 16, 2025 am 12:03 AM

PHP和Python各有優劣,選擇取決於項目需求。 1)PHP適合Web開發,易學,社區資源豐富,但語法不夠現代,性能和安全性需注意。 2)Python適用於數據科學和機器學習,語法簡潔,易學,但執行速度和內存管理有瓶頸。

PHP的目的:構建動態網站PHP的目的:構建動態網站Apr 15, 2025 am 12:18 AM

PHP用於構建動態網站,其核心功能包括:1.生成動態內容,通過與數據庫對接實時生成網頁;2.處理用戶交互和表單提交,驗證輸入並響應操作;3.管理會話和用戶認證,提供個性化體驗;4.優化性能和遵循最佳實踐,提升網站效率和安全性。

PHP:處理數據庫和服務器端邏輯PHP:處理數據庫和服務器端邏輯Apr 15, 2025 am 12:15 AM

PHP在數據庫操作和服務器端邏輯處理中使用MySQLi和PDO擴展進行數據庫交互,並通過會話管理等功能處理服務器端邏輯。 1)使用MySQLi或PDO連接數據庫,執行SQL查詢。 2)通過會話管理等功能處理HTTP請求和用戶狀態。 3)使用事務確保數據庫操作的原子性。 4)防止SQL注入,使用異常處理和關閉連接來調試。 5)通過索引和緩存優化性能,編寫可讀性高的代碼並進行錯誤處理。

您如何防止PHP中的SQL注入? (準備的陳述,PDO)您如何防止PHP中的SQL注入? (準備的陳述,PDO)Apr 15, 2025 am 12:15 AM

在PHP中使用預處理語句和PDO可以有效防範SQL注入攻擊。 1)使用PDO連接數據庫並設置錯誤模式。 2)通過prepare方法創建預處理語句,使用佔位符和execute方法傳遞數據。 3)處理查詢結果並確保代碼的安全性和性能。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它們
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),