백엔드 개발 PHP 튜토리얼 Nginx에서 HTTP 요청 처리

Nginx에서 HTTP 요청 처리

Aug 08, 2016 am 09:30 AM
gt http request return

개요

Nginx의 초기 시작 프로세스 중에 worker 작업자 프로세스는 이벤트 모듈의 ngx_event_process_init 메서드를 호출합니다. 각 리스너 소켓 ngx_listening_tngx_connection_t 연결을 할당하고 연결에 대한 읽기 이벤트에 대한 콜백 메소드를 설정합니다. handlerngx_event_accept이고 읽기 이벤트는 epoll 이벤트 메커니즘에 마운트되어 이 시점에서 읽기 가능한 이벤트가 수신 소켓 연결에서 발생할 때까지 기다립니다. , Nginx는 클라이언트의 요청을 수신하고 처리할 수 있습니다. 청취 소켓 연결에서 읽기 가능한 이벤트가 발생하면, 즉 연결 시 클라이언트의 연결 요청이 있으면 읽기 이벤트의 handler 콜백 메소드 ngx_event_accept가 시작됩니다. . 클라이언트로부터 연결 요청을 받기 위해 ngx_event_accept 메서드에서 accept() 함수가 호출됩니다. 청취 소켓의 handler 콜백 메소드 ls->handler(c)(콜백 메소드는 ngx_http_init_connection입니다). 따라서 연결이 성공적으로 설정된 후 ngx_http_init_connection 메서드가 연결에 대한 요청 데이터 처리를 시작합니다.

HTTP

요청 메시지 수신

HTTP

요청을 수신하기 전에 성공적으로 설정된 연결이 먼저 초기화됩니다. ngx_http_init_connection 이 함수의 기능은 읽기 및 쓰기 이벤트에 대한 콜백 메소드를 설정하는 것입니다. 실제로 쓰기 이벤트에 대한 콜백 메소드는 HTTP 요청 과정.

ngx_http_init_connection

함수 실행 흐름: 현재 연결

핸들러
    에서 이벤트를 쓰기 위한 콜백 메서드 설정
  • ngx_http_empty_handler(실제로 이 메소드는 어떤 작업도 수행하지 않음) 현재 연결 읽기 이벤트 handler
  • 의 콜백 메소드를
  • ngx_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
파일에 다음과 같이 정의됩니다.

연결을 처음으로 읽을 수 있습니다. 이벤트가 발생하면 ngx_http_wait_request_handler 함수가 호출됩니다. 이 함수의 기능은

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

로그인 후 복사
요청을 초기화하는 것입니다. 대신, 현재 연결이 성공한 후에 요청을 초기화합니다. 실제 초기화 작업은 클라이언트의 실제 요청 데이터가 해당 소켓에 수신된 것으로 확인된 경우에만 수행됩니다. 이는 불필요한 메모리 소모를 줄일 수 있습니다(클라이언트가 연결 성공 후 실제 데이터 통신을 수행하지 않는 경우, 이때

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 < 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 函数执行流程:

  • 원래 요청 참조 계산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에 응답합니다. body;
  • 현재 요청 ngx_http_request_t 구조 request_body 멤버를 할당하고, 요청 패킷 본문을 받을 준비가 되었습니다.
  • 요청의 내용을 확인하세요. -length 헤더, 요청 헤더인 경우 content-length 필드가 0보다 작으면 요청 패킷 본문을 계속 수신할 필요가 없음을 의미합니다(즉, 전체 요청 패킷 본문이 수신됨) post_handler 콜백이 직접 실행되고 NGX_OK가 현재 함수에서 반환됩니다. 요청 헤더의
  • content-length
  • 필드가 0보다 크면 요청 패키지 본문을 계속 수신해야 함을 의미합니다. 먼저 현재 요청 ngx_http_request_theader_in 멤버에 처리되지 않은 데이터가 포함되어 있는지 확인합니다. 처리되지 않은 데이터가 있으면 header_in 버퍼가 요청을 수신하고 있음을 의미합니다. 요청 헤더가 요청보다 먼저 수신되었기 때문에 HTTP 요청 헤더 수신 중에 요청 본문을 미리 수신할 수 있기 때문에 이 기간 동안 요청 헤더를 미리 수신했습니다. 요청 패키지 바디인 버퍼에 처리되지 않은 데이터가 있는 경우 바디를 수신합니다. 미리 수신한 요청 패킷 본문인
      header_in
    • 버퍼에 처리되지 않은 데이터가 있는 경우 먼저 버퍼 요청 패킷 길이 preread가 다음보다 큰지 확인하세요. 요청 본문 길이 content-length 필드가 이보다 크면 완료되었음을 의미합니다. HTTP 패키지 본문 요청, 계속 수신할 필요가 없으면 post_handler 콜백 메소드를 실행합니다.
    • header_in buffer 즉, 미리 수신된 요청 패킷 본문이지만 버퍼 요청 패킷 본문 길이
    • preread가 요청 패킷 본문 길이보다 작습니다. content-length 필드는 수신된 요청 패킷 본문이 불완전하므로 요청 패킷 본문을 계속 수신해야 함을 나타냅니다.통화 기능 ngx_http_request_body_filte 수신된 요청 본문을 구문 분석하고 요청에 마운트합니다. ngx_http_request_t r request_body->bufs, header_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의 읽기 이벤트 콜백 메소드는 ngx_http_read_client_request_body_handler이고, 쓰기 이벤트 write_event_handler 콜백 메소드는 ngx_http_request_empty_handler입니다(즉, 어떤 작업도 수행하지 않습니다. ) 그런 다음 메서드를 호출합니다. ngx_http_do_read_client_request_body 정말 받았습니다 HTTP 요청 본문,
src/http/ngx_http_request_body.c

파일의 ngx_http_read_client_request_body 정의는 다음과 같습니다: /* HTTP 요청 본문 수신*/ ngx_int_t ngx_http_read_client_request_body(ngx_http_request_t *r, ngx_http_client_body_handler_pt post_handler) { size_t 미리 읽기; ssize_t 크기; ngx_int_t rc; ngx_buf_t *b; ngx_chain_t 출력, *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 메소드에 의해 참조 횟수가 계속 유지됩니다. */ /* 원래 요청의 참조 횟수를 1만큼 늘립니다. */ r->메인->카운트++; #if (NGX_HTTP_SPDY) if (r->spdy_stream && r == r->main) { rc = ngx_http_spdy_read_request_body(r, post_handler); 끝났어; } #endif /* HTTP 요청 본문이 처리되지 않으면 request_body 구조가 할당되지 않고 처리될 때만 할당됩니다 */ /* * 현재 HTTP 요청이 원래 요청이 아니거나 HTTP 요청 본문을 읽거나 삭제한 경우 * 그런 다음 HTTP 모듈의 post_handler 콜백 메소드를 직접 실행하고 NGX_OK를 반환합니다. */ if (r != r->main || r->request_body || r->discard_body) { post_handler(r); NGX_OK를 반환합니다. } /* * ngx_http_test_expect는 클라이언트가 Expect:100-continue 헤더를 전송하는지 확인하는 데 사용됩니다. * 클라이언트가 요청 본문 데이터를 보낼 것으로 예상된다는 것을 나타내기 위해 이 헤더를 보낸 경우 서버는 반환합니다.

본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

인기 기사

R.E.P.O. 에너지 결정과 그들이하는 일 (노란색 크리스탈)
4 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 최고의 그래픽 설정
4 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 아무도들을 수없는 경우 오디오를 수정하는 방법
4 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 채팅 명령 및 사용 방법
4 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

화웨이 GT3 Pro와 GT4의 차이점은 무엇입니까? 화웨이 GT3 Pro와 GT4의 차이점은 무엇입니까? Dec 29, 2023 pm 02:27 PM

많은 사용자들이 스마트 시계를 선택할 때 Huawei 브랜드를 선택하게 됩니다. 그 중 Huawei GT3pro와 GT4가 가장 인기 있는 선택입니다. 두 제품의 차이점을 궁금해하는 사용자가 많습니다. Huawei GT3pro와 GT4의 차이점은 무엇입니까? 1. 외관 GT4: 46mm와 41mm, 재질은 유리 거울 + 스테인레스 스틸 본체 + 고해상도 섬유 후면 쉘입니다. GT3pro: 46.6mm 및 42.9mm, 재질은 사파이어 유리 + 티타늄 본체/세라믹 본체 + 세라믹 백 쉘입니다. 2. 건강한 GT4: 최신 Huawei Truseen5.5+ 알고리즘을 사용하면 결과가 더 정확해집니다. GT3pro: ECG 심전도, 혈관 및 안전성 추가

http 상태 코드 520은 무엇을 의미합니까? http 상태 코드 520은 무엇을 의미합니까? Oct 13, 2023 pm 03:11 PM

HTTP 상태 코드 520은 서버가 요청을 처리하는 동안 알 수 없는 오류가 발생하여 더 구체적인 정보를 제공할 수 없음을 의미합니다. 서버가 요청을 처리하는 동안 알 수 없는 오류가 발생했음을 나타내는 데 사용됩니다. 이는 서버 구성 문제, 네트워크 문제 또는 기타 알 수 없는 이유로 인해 발생할 수 있습니다. 이는 일반적으로 서버 구성 문제, 네트워크 문제, 서버 과부하 또는 코딩 오류로 인해 발생합니다. 상태 코드 520 오류가 발생하면 웹사이트 관리자나 기술 지원팀에 문의하여 자세한 정보와 지원을 받는 것이 가장 좋습니다.

C 언어의 return 사용법에 대한 자세한 설명 C 언어의 return 사용법에 대한 자세한 설명 Oct 07, 2023 am 10:58 AM

C 언어에서 return의 사용법은 다음과 같습니다. 1. 반환 값 유형이 void인 함수의 경우 return 문을 사용하여 함수 실행을 조기에 종료할 수 있습니다. 2. 반환 값 유형이 void가 아닌 함수의 경우 return 문은 함수 실행을 종료하는 것입니다. 결과는 호출자에게 반환됩니다. 3. 함수 실행을 조기에 종료합니다. 함수 내부에서는 return 문을 사용하여 함수 실행을 조기에 종료할 수 있습니다. 함수가 값을 반환하지 않는 경우.

http 상태 코드 403이란 무엇입니까? http 상태 코드 403이란 무엇입니까? Oct 07, 2023 pm 02:04 PM

HTTP 상태 코드 403은 서버가 클라이언트의 요청을 거부했음을 의미합니다. http 상태 코드 403에 대한 해결 방법은 다음과 같습니다. 1. 서버에 인증이 필요한 경우 올바른 자격 증명이 제공되었는지 확인합니다. 2. 서버가 IP 주소를 제한한 경우 클라이언트의 IP 주소가 제한되어 있거나 블랙리스트에 없습니다. 3. 파일 권한 설정을 확인하십시오. 403 상태 코드가 파일 또는 디렉토리의 권한 설정과 관련되어 있으면 클라이언트가 해당 파일 또는 디렉토리에 액세스할 수 있는 권한이 있는지 확인하십시오. 등.

Nginx 프록시 관리자를 사용하여 HTTP에서 HTTPS로 자동 점프를 구현하는 방법 Nginx 프록시 관리자를 사용하여 HTTP에서 HTTPS로 자동 점프를 구현하는 방법 Sep 26, 2023 am 11:19 AM

NginxProxyManager를 사용하여 HTTP에서 HTTPS로의 자동 점프를 구현하는 방법 인터넷이 발전하면서 점점 더 많은 웹사이트가 HTTPS 프로토콜을 사용하여 데이터 전송을 암호화하여 데이터 보안과 사용자 개인 정보 보호를 향상시키기 시작했습니다. HTTPS 프로토콜에는 SSL 인증서 지원이 필요하므로 HTTPS 프로토콜 배포 시 특정 기술 지원이 필요합니다. Nginx는 강력하고 일반적으로 사용되는 HTTP 서버 및 역방향 프록시 서버이며 NginxProxy

웹 페이지 리디렉션의 일반적인 애플리케이션 시나리오를 이해하고 HTTP 301 상태 코드를 이해합니다. 웹 페이지 리디렉션의 일반적인 애플리케이션 시나리오를 이해하고 HTTP 301 상태 코드를 이해합니다. Feb 18, 2024 pm 08:41 PM

HTTP 301 상태 코드의 의미 이해: 웹 페이지 리디렉션의 일반적인 응용 시나리오 인터넷의 급속한 발전으로 인해 사람들은 웹 페이지 상호 작용에 대한 요구 사항이 점점 더 높아지고 있습니다. 웹 디자인 분야에서 웹 페이지 리디렉션은 HTTP 301 상태 코드를 통해 구현되는 일반적이고 중요한 기술입니다. 이 기사에서는 HTTP 301 상태 코드의 의미와 웹 페이지 리디렉션의 일반적인 응용 프로그램 시나리오를 살펴봅니다. HTTP301 상태 코드는 영구 리디렉션(PermanentRedirect)을 나타냅니다. 서버가 클라이언트의 정보를 받을 때

수정: Windows 11에서 캡처 도구가 작동하지 않음 수정: Windows 11에서 캡처 도구가 작동하지 않음 Aug 24, 2023 am 09:48 AM

Windows 11에서 캡처 도구가 작동하지 않는 이유 문제의 근본 원인을 이해하면 올바른 솔루션을 찾는 데 도움이 될 수 있습니다. 캡처 도구가 제대로 작동하지 않는 주요 이유는 다음과 같습니다. 초점 도우미가 켜져 있습니다. 이렇게 하면 캡처 도구가 열리지 않습니다. 손상된 응용 프로그램: 캡처 도구가 실행 시 충돌하는 경우 응용 프로그램이 손상되었을 수 있습니다. 오래된 그래픽 드라이버: 호환되지 않는 드라이버가 캡처 도구를 방해할 수 있습니다. 다른 응용 프로그램의 간섭: 실행 중인 다른 응용 프로그램이 캡처 도구와 충돌할 수 있습니다. 인증서가 만료되었습니다. 업그레이드 프로세스 중 오류로 인해 이 문제가 발생할 수 있습니다. 이 문제는 대부분의 사용자에게 적합하며 특별한 기술 지식이 필요하지 않습니다. 1. Windows 및 Microsoft Store 앱 업데이트

빠른 적용: 여러 파일의 PHP 비동기 HTTP 다운로드에 대한 실제 개발 사례 분석 빠른 적용: 여러 파일의 PHP 비동기 HTTP 다운로드에 대한 실제 개발 사례 분석 Sep 12, 2023 pm 01:15 PM

빠른 적용: PHP의 실제 개발 사례 분석 여러 파일의 비동기 HTTP 다운로드 인터넷의 발전으로 파일 다운로드 기능은 많은 웹 사이트와 응용 프로그램의 기본 요구 사항 중 하나가 되었습니다. 여러 파일을 동시에 다운로드해야 하는 시나리오의 경우 기존 동기 다운로드 방법은 비효율적이고 시간이 많이 걸리는 경우가 많습니다. 이러한 이유로 PHP를 사용하여 HTTP를 통해 여러 파일을 비동기적으로 다운로드하는 것이 점점 더 일반적인 솔루션이 되었습니다. 본 글에서는 실제 개발 사례를 통해 PHP 비동기 HTTP를 활용하는 방법을 자세히 분석해 보겠습니다.

See all articles