上一篇分析了 echo 模块的整体结构和大致执行流程,本篇分别对此模块用到的技术细节 进行详细的分析。

  • echo 模块各个函数识别的错误码及处理方式

echo_location[_async]

echo_locationecho_location_async 的区别:

Just like the echo_location_async directive, but echo_location issues subrequests in series rather than in parallel. That is, the content handler directives following this directive won't be executed until the subrequest issued by this directive completes.

另外,内部实现原理上,echo_subrequest[_async] 和这两个指令除了提供更多的选项 外,处理流程上大同小异。

在实现上,echo_locationecho_location_async 的差异是执行这两条命令的请求 在创建了子请求后,是否等待子请求完成后才继续下面的操作:

  • echo_location 命令在子请求创建成功后 “挂起” 当前请求,直到其子请求完成后,再 通过 post_subrequest 回调函数即 ngx_http_echo_post_subrequest 继续该请求的执 行。

    case echo_opcode_echo_location:
        ...
        return ngx_http_echo_exec_echo_location(r, ctx, computed_args);
    
  • echo_location_async 命令在子请求创建成功后继续处理当前请求。Nginx 同时处理子 请求逻辑,然后依靠 postpone filter 组织和调整最终的响应内容。

    case echo_opcode_echo_location_async:
        ...
        rc = ngx_http_echo_exec_echo_location_async(r, ctx, computed_args);
        break;
    

patch for issue #26

    ngx_int_t
    ngx_http_echo_exec_echo_request_body(ngx_http_request_t *r,
        ngx_http_echo_ctx_t *ctx)
    {
        ngx_buf_t       *b;
        ngx_chain_t     *cl, *out, *chain;
        ngx_chain_t     **ll;

        if (!r->request_body || !r->request_body->bufs) {
            return NGX_OK;
        }

        out = NULL;
        ll = &out;

        for (cl = r->request_body->bufs; cl; cl = cl->next) {

            chain = ngx_alloc_chain_link(r->pool);
            if (chain == NULL) {
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
            }

            b = ngx_calloc_buf(r->pool);
            if (b == NULL) {
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
            }

            if (cl->buf->in_file) {
                b->in_file = 1;
                b->tag = (ngx_buf_tag_t) &ngx_http_echo_exec_echo_request_body;
                b->file_last = cl->buf->file_last;
                b->file = cl->buf->file;

            } else {
                b->temporary = 1;
                b->tag = (ngx_buf_tag_t) &ngx_http_echo_exec_echo_request_body;
                b->start = cl->buf->pos;
                b->pos = cl->buf->pos;
                b->last = cl->buf->last;
                b->end = cl->buf->end;
            }

            chain->buf = b;
            chain->next = NULL;

            *ll = chain;
            ll = &chain->next;
        }

        return ngx_http_echo_send_chain_link(r, ctx, out);
    }

ngx_http_request_t::count

From <深入理解 Nginx>: 使用引用计数是为了让 Nginx 知道 HTTP 模对于该请求有独立 的异步处理机制 (流程)。每个操作在 “认为” 自身的动作结束时,都得最终调用 ngx_http_close_request 方法。

  • 流程就是 顺畅执行 下来的一系列动作 (不满意这个总结)

  • count - 引用计数,用于防止 ngx_http_request_t 在处理某种事件的过程中被销 毁。请求创建时,此值为 1。任何独立于主流程之外的异步流程都需要将其加 1,每个流 程结束后,都要调用 ngx_http_finalize_request 正式结束此流程。

    • 例 1:dav 模块对 PUT 方法的处理在 ngx_http_dav_handler 中完成。

      • 此法 函数调用 ngx_http_read_client_request_body,增加 count 数, 相当于开启了新的流程,此流程需要新事件的触发。

      • ngx_http_dav_handler 主流程正常返回 NGX_DONE 后,其调用者 ngx_http_core_content_phase 对当前请求调用 ngx_http_finalize_request 将主流程引用计数从 count 中减去。

      • ngx_http_read_client_request_body 流程处理完足够的事件后,调用 post_handler 也就是 ngx_http_dav_put_handler,此 handler 完成后 会再次调用 ngx_http_finalize_request 将此流程的引用计数从 count 中 减去。

      • 此时引用计数为 0,Nginx 真正销毁此请求,释放资源。

    • 例 2:在 ngx_http_upstream_create 中创建上游连接时,需对老连接进行清理。

      • 主流程是为新上游连接结构体 ngx_http_upstream_t 分配内存空间。

      • 当发现上次遗留的上游连接未被清理时,调用 ngx_http_upstream_cleanup 开始新的清理流程。清理函数本身不需要等待新的事件,但是它会在清理结束后 调用 ngx_http_finalize_request 减少引用计数并试图销毁此请求。

      • 为了清理过程后请求的有效性。在清理开始前,增加引用计数。

    • 例 3:proxy 模块的后端连接开启函数 ngx_http_proxy_handler。和 例 1 大同小异。

  • 一句话总结:有逻辑开启流程,引用数就会加 1,此流程结束时,必然有逻辑将引用数 减1。拿 ngx_http_read_client_request_body 为例,函数开始就增加了引用计数,在 碰到异常结果时,可认为新流程并未正常开始,在此函数返回前就会将引用计数减一;如 果流程正常结束,引用计数就需要 post_handler 负责维护;如果一次函数调用未完成 处理,这时它会启用读事件处理函数 ngx_http_read_client_request_body_handler 来 继续这个流程的处理,对引用计数的处理方式和前面两种情况类似。

  • 代码摘录 (1.4.2):

    ngx_int_t
    ngx_http_read_client_request_body(ngx_http_request_t *r,
        ngx_http_client_body_handler_pt post_handler)
    {
        ...
        r->main->count++;
        ...
        rc = ngx_http_do_read_client_request_body(r);
        ...
        if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
            r->main->count--;
        }
    
        return rc;
    }
    
    static void
    ngx_http_read_client_request_body_handler(ngx_http_request_t *r)
    {
        ...
        rc = ngx_http_do_read_client_request_body(r);
    
        if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
            ngx_http_finalize_request(r, rc);
        }
    }
    
    static ngx_int_t
    ngx_http_dav_handler(ngx_http_request_t *r)
    {
        ...
        switch (r->method) {
    
        case NGX_HTTP_PUT:
    
            ...
            rc = ngx_http_read_client_request_body(r, ngx_http_dav_put_handler);
    
            if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
                return rc;
            }
    
            return NGX_DONE;
        ...
    }
    
    static void
    ngx_http_dav_put_handler(ngx_http_request_t *r)
    {
        ...
        ngx_http_finalize_request(r, XXXX);
        return;
    }
    

request_body

  • 在执行读取命令时,r->request_body->bufs 要么有了全部数据,要么还没有任何数 据。所以,不会出现发送它的内容过程中,它被修改的情况。upstream 都是直接使用 r->request_body.bufs 作用发送 chain

  • 请求包体使用专用的结构体 ngx_http_request_body_t 表示,在每个请求中有些类型 的字段 ngx_http_request_body_t *request_body,并且默认值为 NULL。此字段内存 在 ngx_http_read_client_request_body 中分配。

    struct ngx_http_chunked_s {
        ngx_uint_t      state;
        off_t           size;    /* 当前 chunk 大小 */
        off_t           length;
    };
    
    typedef struct {
        ngx_temp_file_t                 *temp_file; /* 存储包体的临时文件 */
        ngx_chain_t                     *bufs; /* 等处理 buf 链,buf 可能指
                                                  向内存空间,可能只是文件的
                                                  描述 */
        ngx_buf_t                       *buf; /* 当前可用 ngx_buf_t */
        off_t                           rest; /* 请求包体还剩余的字节数 */
        ngx_chain_t                     *free;
        ngx_chain_t                     *busy; /* busy 和 free 用来跟踪 buf
                                                  的处理情况 */
        ngx_http_chunked_t              *chunked; /* ngx_http_parse_chunked
                                                    用,保存 chunk 解码状态 */
        ngx_http_client_body_handler_pt post_handler; /* 包体接收操作完成后
                                                         的回调函数 */
    } ngx_http_request_body_t;
    
  • HTTP 请求包体相关的概念:

    • Expect - request-header. It is used to indicate that particular server behavior are required by the client.

    • 100 Continue - response-status

      This means that the server has received the request headers, and that
      the client should proceed to send the request body (in the case of a
      request for which a body needs to be sent; for example, a POST request).
      If the request body is large, sending it to a server when a request has
      already been rejected based upon inappropriate headers is inefficient.
      To have a server check if the request could be accepted based on the
      request's headers alone, a client must send `Expect: 100-continue` as a
      header in its initial request and check if a 100 Continue status code
      is received in response before continuing (or receive 417 Expectation
      Failed and not continue).
      
    • ngx_http_test_expect - 用于检测客户端是否有 Expect: 100-continue包头, 如果此包头出现,Nginx 就向客户端发送 HTTP/1.1 100 Continue 的响应,告诉客 户端可以开始发送请求包体数据了。

    • POST - HTTP method. Content-Lenght: xxx, Tranport-Encoding: chunked.

    • 请求的 Content-Length 包头和 Transport-Encoding 包头在 ngx_http_process_request_header 函数中处理。另外,在较老的 Nginx 版本中 是不支持 chunked 传输编码格式的。

      if (r->headers_in.content_length) {
          r->headers_in.content_length_n =
                              ngx_atoof(r->headers_in.content_length->value.data,
                                        r->headers_in.content_length->value.len);
          ...
      }
      ...
      if (r->headers_in.transfer_encoding) {
          if (r->headers_in.transfer_encoding->value.len == 7
              && ngx_strncasecmp(r->headers_in.transfer_encoding->value.data,
                                 (u_char *) "chunked", 7) == 0)
          {
              r->headers_in.content_length = NULL;
              r->headers_in.content_length_n = -1;
              r->headers_in.chunked = 1;
          }
          ...
      }
      
  • 相关函数用途

    • ngx_http_request_body_filter - 解析并存储请求包体数据

    • ngx_http_request_body_chunked_filter - 解析并存储 chunked 编码的请求 包体数据,并在解析过程中判断包体是否超过了 client_max_body_size。一个 chunk 被成功识别后,从 r->request_body->free 中申请 chain-buf 对其进 行描述,并将 chain-buf 追加到 r->request_body->busyr->request_body->bufs 中 (实际上 chain 被拷贝)

    • ngx_http_request_body_length_filter - 解析并存储长度己由 Content-Length 标明的请求包体数据。它从 r->request_body->free 链中申请 chain-buf,描述 可用数据信息,并将 chain-buf 追加到 r->request_body->busyr->request_body->bufs 中 (实际上 chain 被拷贝)

    • ngx_http_request_body_save_filter - 将存有数据的 chain 进行拷贝 (只有 chain 被拷贝) 并追加到 r->request_body->bufs

    • ngx_chain_update_chains - 使用 r->request_body->freer->request_body->busy 跟踪数据的使用情况。freebusychain 并 不会由其它函数保存,而 chain 指向的 buf,也会被其它函数自己申请的 chain 引用 (这样,ngx_chain_update_chains 才能知道 buf 的使用情况)。

  • Nginx 使用 ngx_http_read_client_request_body 读取请求包体,用于数据缓存的内 存块大小由 client_body_buffer_size 指定,超出此内存缓存的部分被写入临时文件。 如果打开了 client_body_in_file_only,也会将包体全部写入临时文件。

    1. 如果客户端使用了 Expect: 100-continue 包头,发送 100 Continue 响应给 客户端,告知它可以开始发送包体数据

    2. 如何 r->header_in 缓存中己经读入了部分包体数据,将这部分数据追加到 r->request_body->bufs

    3. 如果包体已经接收完成,检查 client_body_in_file_only 配置项是否打开。 如果配置项打开,将 r->request_body->bufs 中的包体数据写入临时文件,并用它 保存用于描述临时文件的 ngx_buf_t,并执行步骤 8;如果配置项未打开,直执 行步骤 8

    4. 申请 client_body_buffer_size 大小的 (如果此时能够判断剩余包体小于此配置 值,可以减小此值) 用于缓存包体数据的内存块,使用 r->request_body->buf 引用

    5. 从连接读取包体数据 (c->recv)

    6. 如果 r->request_body->buf 已满,将其中数据写入临时文件,并清空 r->request_body->bufs。重置 r->request_body->buf

    7. 请求包体读取完成后,检查 client_body_in_file_only。如果此配置项打开或 者在读取过程中已经创建了临时文件,将 r->bufs 中缓存的数据全部写入临时文件。 结果就是,请求包体要么都在内存缓存区中,要么存令在临时文件中

    8. 请求包体接收处理完成,调用 post_handler

ngx_buf_t

  • ngx_buf_t *shadow -

  • bit temporary - the buf's content could be changed

  • bit memory - the buf's content is in a memory cache or in a read-only memory and must not be changed.
  • bit mmap - the buf's content is mmap()ed and must not be changed
  • bit in_file - data represented by this ngx_buf_t resides in disk file

  • bit flush - send data out immediately, even it is not the last_buf or is currently being postponed.

  • bit sync - 找 agentzh 请教一下;agentzh 解答如下:

    • The sync flag in ngx_buf_t is just for intentional empty bufs. Many nginx output filters could create "empty bufs" intentional to simplify the implementation of streaming filtering algorithm. Nginx will complain on pure "empty bufs" (zero sized and with no flags set).

    • Forgot to mention that the sync bufs generated in ngx_echo are just for working with output filter modules like ngx_xss: https://github.com/agentzh/xss-nginx-module

  • bit last_buf - the very last part of response

    • write filter will send data before it if they were postponed.
    • gunzip filter knows when to add Z_FINISH because of it.
    • image filter knows where the last byte of image is according to it.
    • chunked filter appends the last chunk of chunked encoding when meets it.
  • bit last_in_chain - last buf in this chain, make foreach chain easier.

  • bit last_shadow

  • bit temp_file

  • ngx_http_send_special(r, NGX_HTTP_FLUSH)

  • ngx_http_send_special(r, NGX_HTTP_LAST);
    ngx_int_t
    ngx_http_send_special(ngx_http_request_t *r, ngx_uint_t flags)
    {
        ...
        b = ngx_calloc_buf(r->pool);
        ...
        if (flags & NGX_HTTP_LAST) {
    
            if (r == r->main && !r->post_action) {
                b->last_buf = 1;
    
            } else {
                b->sync = 1;
                b->last_in_chain = 1;
            }
        }
    
        if (flags & NGX_HTTP_FLUSH) {
            b->flush = 1;
        }
    
        out.buf = b;
        out.next = NULL;
    
        return ngx_http_output_filter(r, &out);
    }
    

ngx_chain_t

  • 相关操作函数

    • ngx_alloc_chain_link - 从内存池申请一个 ngx_chain_t

    • ngx_free_chain - 释放 ngx_chain_t 到内存池 free list,使其可复用

    • ngx_create_chain_of_bufs - 一次性从内存池申请多个 chain-buf-memory

    使用 ngx_bufs_t 描述 ngx_buf_t 的个数和其维护的内存块大小

    • ngx_chain_add_copy(p, **chain, *in) - 拷贝 in*chain 中,复用 ngx_buf_t

    • ngx_chain_get_free_buf(p, **free) - 从 free list 或内存池中申请 chain-buf

    • ngx_chain_update_chains(p, **free, **busy, **out, tag) - 维护 free listbusy free,以复用 chain-buf

  • in 追加到 out (拷贝 chain-buf,不拷贝实际数据)

    ngx_buf_t *b;
    ngx_chain_t *cl, *chain, *out, **ll;
    
    for (cl = out; cl; cl = cl->next) {
        ll = &cl->next;
    }
    
    for (cl = in; cl; cl = cl->next) {
        chain = ngx_chain_get_free_buf(r->pool, &free);
    
        b = chain->buf;
        /* operations on b */
    
        *ll = chain;
        ll = &chain->next;
    }
    
    *ll = NULL;
    
Comments

不要轻轻地离开我,请留下点什么...

comments powered by Disqus

Published

Category

Nginx

Tags

Contact