Nginx源码阅读笔记-处理HTTP请求

2019-02-13
16分钟阅读时长

前面分析了nginx如何读取一个HTTP请求如何查询到HTTP对应的配置,本节分析如何处理HTTP请求。

处理HTTP请求的11个阶段

nginx将处理HTTP请求划分为了11个阶段,原因在于nginx是一个重度模块化的系统,划分为不同阶段以后,不同的模块可以根据自己的需求在相应的模块中添加自己的处理函数。

简单看看这11个模块的定义:

typedef enum {
  // 在接收到完整的HTTP头部后处理的HTTP阶段
  NGX_HTTP_POST_READ_PHASE = 0,

  // 在将请求的URI与location表达式匹配前,修改请求的
  // URI(所谓的重定向)是一个独立的HTTP阶段
  NGX_HTTP_SERVER_REWRITE_PHASE,

  // 根据请求的URL寻找匹配的location表达式,这个阶段
  // 只能由ngx_http_core_module模块实现,不建议其他HTTP
  // 模块模块重新定义这一阶段的行为
  NGX_HTTP_FIND_CONFIG_PHASE,

  // 在NGX_HTTP_FIND_CONFIG_PHASE阶段寻找到匹配的location
  // 之后再修改请求的URI
  NGX_HTTP_REWRITE_PHASE,

  // 这一阶段用于在rewrite重写URL后,防止错误的nginx配置导致
  // 死循环(递归地修改URI),因此,这一阶段仅由ngx_http_core_module
  // 模块处理。目前,控制死循环的方法就是看rewrite次数,超过一定阈值
  // 就认为出现了死循环,返回500
  NGX_HTTP_POST_REWRITE_PHASE,

  // 表示在处理NGX_HTTP_ACCESS_PHASE阶段决定请求的访问权限前,HTTP模块可以介入的处理阶段
  NGX_HTTP_PREACCESS_PHASE,

  // 这个阶段用于让HTTP模块判断是否允许这个请求访问Nginx服务器
  NGX_HTTP_ACCESS_PHASE,

  // 在NGX_HTTP_ACCESS_PHASE阶段中,当HTTP模块的handler处理函数返回不允许访问的错误码时(
  // 实际就是NGX_HTTP_FORBIDDEN或者NGX_HTTP_UNAUTHORIZED),这里将负责向用户发送拒绝服务的
  // 错误响应,因此这个阶段实际上用于给NGX_HTTP_ACCESS_PHASE阶段收尾
  NGX_HTTP_POST_ACCESS_PHASE,

  // 这个阶段完全为try_files配置项而设立的,当HTTP访问静态文件资源时,try_files配置项可以
  // 使这个请求顺序地访问多个静态文件资源,如果某一次访问失败,则继续访问try_files中指定的
  // 下一个静态资源。
  NGX_HTTP_PRECONTENT_PHASE,

  // 用于处理HTTP请求内容的阶段,这是大部分HTTP模块最愿意介入的阶段
  NGX_HTTP_CONTENT_PHASE,

  // 处理完请求记录日志的阶段。
  NGX_HTTP_LOG_PHASE
} ngx_http_phases;

这11个阶段里,有一些是可以由模块开发者插入自己的处理函数,有一些只能使用nginx的http框架的实现。另外,每个阶段并不是一定只能有一个处理函数,有的可以提供多个处理函数,在同一个阶段中顺序被调用。

核心数据结构

ngx_http_phase_handler_t

定义每阶段相关的处理方法,在数据结构ngx_http_phase_handler_t中,包括以下成员:

  • ngx_http_phase_handler_pt checker:在处理任何一个HTTP阶段的时候,HTTP框架将会在checker方法已经实现的情况下首先调用该方法来处理请求。这种情况下不是直接调用handler方法,而是在checker中间接调用handler方法。但是所有的checker方法都是由框架中的ngx_http_core_module模块实现的,普通的HTTP模块无法重定义checker方法。
  • ngx_http_handler_pt handler:定义处理某阶段HTTP请求的回调函数。
  • ngx_uint_t next:将要执行的下一个HTTP处理阶段的索引。有了这个索引值,阶段的执行不见得一定得按照顺序来,可以向前或者向后跳转阶段来处理。

ngx_http_phase_engine_t

ngx_http_phase_engine_t结构体用于保存所有ngx_http_phase_handler_t组成的数组,其成员如下:

  • ngx_http_phase_handler_t *handlers:存放handler的数组。
  • ngx_uint_t server_rewrite_index:表示NGX_HTTP_SERVER_REWRITE_PHASE阶段第一个handler处理方法在handlers数组中的索引,用于在执行HTTP请求的任何阶段快速跳转到NGX_HTTP_SERVER_REWRITE_PHASE阶段处理请求。
  • ngx_uint_t location_rewrite_index:表示NGX_HTTP_REWRITE_PHASE阶段第一个处理方法在handlers数组中的索引。

ngx_http_core_main_conf_t中与HTTP阶段相关的成员

在ngx_http_core_main_conf_t结构体中,与HTTP处理阶段相关的成员有如下两个:

  • ngx_http_phase_engine_t phase_engine:控制运行过程中一个HTTP请求所要经过的HTTP处理阶段,将配合ngx_http_request结构体中的phase_handler成员使用。
  • ngx_http_phase_t phases[NGX_HTTP_LOG_PHASE + 1]:用于在HTTP框架初始化时帮助各个HTTP模块在任意阶段中添加HTTP处理方法,由11个成员组成(因为有11个HTTP阶段)的数组,其中每个成员对应一个HTTP阶段。在HTTP框架初始化完毕之后,运行过程中的phases数组是无用的。

而ngx_http_phase_t结构体实际上就是一个存放handler的数组:

typedef struct {
  ngx_array_t                handlers;
} ngx_http_phase_t;

ngx_http_core_main_conf_t.phases数组,仅在启动时用于保存初始化注册进来的handler只用,是一个”静态“的数据,后续不会再做变动了。

核心流程

介绍完处理HTTP请求相关的核心数据结构,现在回到处理HTTP请求的流程分析中。

在前面分析读HTTP请求部分,提到过当读取完毕HTTP请求,nginx将进入到ngx_http_process_request函数正式开始处理HTTP请求。

由于到这里已经读取完毕HTTP的请求行以及header,所以不再有状态机机制,来看看这部分的核心流程:

  • 由于已经读取完毕HTTP请求行以及HTTP头部数据,此时不再存在接收HTTP请求头超时的问题,因此需要将读事件的定时器删除。
  • 由于也不再需要继续读取HTTP请求行以及头部,因此重新设置当前连接的读、写事件回调函数为ngx_http_request_handler。
  • 将ngx_http_request_t结构体的read_event_handler回调函数设置为ngx_http_block_reading,这个函数实际上并不做任何事情。可以理解为:在当前HTTP请求没有处理结束之前,即使再次有读事件被触发,也不做任何处理,实际上相当于读事件被阻塞了。
  • 接下来调用ngx_http_handler函数。

来看ngx_http_handler函数的流程:

  • 检查ngx_http_request_t结构体的internal标志位,区分以下情况:
    • 0:表示不需要重定向,设置ngx_http_request_t.phase_handler为0,表示从ngx_http_phase_engine_t指定数组的第一个回调方法开始执行。
    • 1:表示需要重定向,设置ngx_http_request_t.phase_handler为phase_engine.server_rewrite_index,server_rewrite_index索引保存的是handler数组中NGX_HTTP_REWRITE_PHASE处理阶段的第一个handler回调函数。phase_handler保存着将要执行的handlers数组中的索引,因此通过修改这个索引值,可以实现HTTP处理阶段的跳转。这里将这个索引值切换为NGX_HTTP_REWRITE_PHASE,意味着无论当前在哪个阶段,都将重新回到NGX_HTTP_REWRITE_PHASE阶段开始再次执行。这是nginx代码中实现请求可以反复rewrite重定向的基础。
  • 将ngx_http_request_t.write_event_handler设置为ngx_http_core_run_phases。
  • 调用ngx_http_core_run_phases函数。

终于来到与HTTP不同阶段处理流程相关的函数ngx_http_core_run_phases了,代码如下:

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;

  cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

  ph = cmcf->phase_engine.handlers;

  while (ph[r->phase_handler].checker) {

    rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);

    if (rc == NGX_OK) {
      return;
    }
  }
}

可以看到该函数的核心流程就是:

  • 根据当前phase_handler索引,取出checker(如果存在的话)进行调用。
  • 返回值为非NGX_OK,意味着将继续向下执行phase_engine的各处理函数;
  • 反之,checker方法返回NGX_OK的时候,意味着控制权交还给nginx的事件模块,等待被IO事件唤醒时再次被触发调用。

处理HTTP请求的11个阶段

下面具体看处理HTTP请求的11个阶段。

HTTP阶段 checker
NGX_HTTP_POST_READ_PHASE ngx_http_core_generic_phase
NGX_HTTP_SERVER_REWRITE_PHASE ngx_http_core_rewrite_phase
NGX_HTTP_FIND_CONFIG_PHASE ngx_http_core_find_config_phase
NGX_HTTP_REWRITE_PHASE ngx_http_core_rewrite_phase
NGX_HTTP_POST_REWRITE_PHASE ngx_http_core_post_rewrite_phase
NGX_HTTP_PREACCESS_PHASE ngx_http_core_generic_phase
NGX_HTTP_ACCESS_PHASE ngx_http_core_access_phase
NGX_HTTP_POST_ACCESS_PHASE ngx_http_core_post_access_phase
NGX_HTTP_TRY_FILES_PHASE ngx_http_core_try_files_phase
NGX_HTTP_CONTENT_PHASE ngx_http_core_content_phase
NGX_HTTP_LOG_PHASE ngx_http_core_generic_phase

NGX_HTTP_POST_READ_PHASE(可添加自定义HTTP模块)

接收完毕HTTP请求头之后的第一个阶段,任何需要在接收完毕请求HTTP头之后添加处理的模块都可以放到这个阶段来。 当前realip模块就是在这个阶段,用于获取客户端的真是IP地址。

本阶段的checker函数是ngx_http_core_generic_phase,该函数也是是NGX_HTTP_POST_READ_PHASE、NGX_HTTP_PREACCESS_PHASE、NGX_HTTP_LOG_PHASE三个阶段的checker函数,根据调用handler的返回值,有以下的处理:

  • NGX_OK:执行下一个HTTP阶段的handler函数,跳过本阶段的下一个handler。
  • NGX_DECLINED:按照注册顺序执行本阶段下一个handler函数。
  • NGX_AGAIN:当前阶段的handler尚未处理结束,将控制权返回事件模块,等待下一次被事件触发再次被调用。
  • NGX_DONE:同NGX_AGAIN。
  • NGX_ERROR:调用ngx_http_finalize_request结束请求。

NGX_HTTP_SERVER_REWRITE_PHASE(可添加自定义HTTP模块)

server级别的URI重写阶段。在使用URI匹配location配置之前,修改URI用于重定向。 本阶段的checker函数为ngx_http_core_rewrite_phase,是NGX_HTTP_SERVER_REWRITE_PHASE和NGX_HTTP_REWRITE_PHASE的checker函数,根据handler返回值有以下处理:

  • NGX_DECLINED:递增phase_handler跳到下一个回调方法,并且返回NGX_AGAIN,因为返回的不是NGX_OK,因此HTTP框架不会把控制权返回给事件框架,而是马上执行下一个handler。
  • NGX_DONE:意味着handler无法在一次调度中处理完这个阶段,此时返回NGX_OK,等待被事件框架再一次唤醒执行。
  • 其他情况:调用ngx_http_finalize_request结束请求。

可以看到,ngx_http_core_generic_phase与ngx_http_core_rewrite_phase的区别在于:后者永远不会跨越同一个HTTP阶段的其他处理方法,就直接跳到下一个阶段来处理请求。原因在于:许多HTTP模块在NGX_HTTP_SERVER_REWRITE_PHASE和NGX_HTTP_REWRITE_PHASE同时处理URL重写这样的业务,HTTP框架认为这两个阶段的HTTP模块是完全平等的,序号靠前的HTTP模块并不优先级更高,不能决定后续模块是否被调用。

NGX_HTTP_FIND_CONFIG_PHASE(不可添加自定义HTTP模块)

用于根据URI查询匹配的location表达式。 参看前面的根据URI查询location一节内容

需要注意的是:一次请求可能会多次来到该阶段,因为被rewrite之后的请求需要重新查找location。

NGX_HTTP_REWRITE_PHASE(可添加自定义HTTP模块)

location级别URI重写阶段,该阶段执行location级别的重写指令,有可能导致重新回到上面的NGX_HTTP_FIND_CONFIG_PHASE阶段。 checker函数同上面的NGX_HTTP_SERVER_REWRITE_PHASE阶段。

NGX_HTTP_POST_REWRITE_PHASE(不可添加自定义HTTP模块)

本阶段用于检查重写URI次数,不能超过10次以避免出现rewrite死循环的情况。本阶段不能自定义HTTP模块,checker函数为ngx_http_core_post_rewrite_phase。

NGX_HTTP_PREACCESS_PHASE(可添加自定义HTTP模块)

来到这一阶段,说明已经根据URI查询到了对应的location,即对应的loc_conf配置已经确定下来,该阶段主要主要用于访问资源控制,如限制频率、链接数等,本阶段的checker仍然是前面的ngx_http_core_generic_phase。

NGX_HTTP_ACCESS_PHASE(可添加自定义HTTP模块)

本阶段与上一阶段的区别在于,使用的checker为ngx_http_core_access_phase,主要用于检测用户发起的请求是否合法,比如IP地址是否允许访问等。

涉及到nginx配置文件中的satisfy配置项,根据本阶段handler的不同返回值,有以下区别:

  • NGX_OK:如果在配置文件中配置了satisfy all,那么将按照顺序执行下一个ngx_http_handler_pt方法;如果配置了satisfy any,那么将会执行下一个ngx_http_phase阶段的第一个ngx_http_handler_pt方法。
  • NGX_DECLINED:按照顺序执行下一个ngx_http_handler_pt方法。
  • NGX_AGAIN, NGX_DONE:当前的ngx_http_handler_pt方法尚未结束,当前方法有机会被再次调用。
  • NGX_HTTP_FORBIDDEN: 如果在配置文件中配置了satisfy any,那么将ngx_http_request_t中的access_code成员设置为返回值。按照顺序执行下一个ngx_http_handler_pt处理方法;如果是satisfy all,那么调用ngx_http_finalize_request结束请求。
// NGX_HTTP_ACCESS_PHASE阶段的处理函数,用于控制用户发起的请求是否合法,
// 如检测客户端的IP地址是否允许被访问
ngx_int_t
ngx_http_core_access_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph)
{
  ngx_int_t                  rc;
  ngx_http_core_loc_conf_t  *clcf;

  // r != r->main表示当前请求只是派生出来的子请求,子请求不需要执行
  // NGX_HTTP_ACCESS_PHASE阶段的处理
  if (r != r->main) {
    r->phase_handler = ph->next;
    return NGX_AGAIN;
  }

  ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
    "access phase: %ui", r->phase_handler);

  rc = ph->handler(r);

  // 返回NGX_DECLINED意味着希望立刻执行下一个handler,
  // 无论是否属于NGX_HTTP_ACCESS_PHASE
  if (rc == NGX_DECLINED) {
    r->phase_handler++;
    return NGX_AGAIN;
  }

  // 返回AGAIN或者DONE意味着当前的NGX_HTTP_ACCESS_PHASE没有一次性执行完毕
  if (rc == NGX_AGAIN || rc == NGX_DONE) {
    return NGX_OK;
  }

  // 以下是返回值不是 NGX_DECLINED、NGX_AGAIN、NGX_DONE的情况

  // 由于NGX_HTTP_ACCESS_PHASE阶段在NGX_HTTP_FIND_CONFIG_PHASE阶段后面,
  // 因此此时请求已经找到了匹配的的location模块
  clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

  if (clcf->satisfy == NGX_HTTP_SATISFY_ALL) { // NGX_HTTP_SATISFY_ALL表示nginx配置文件中配置了satisfy all参数
    // satisfy all要求所有NGX_HTTP_ACCESS_PHASE阶段的handler方法共同作用于该请求

    if (rc == NGX_OK) { // 该模块返回NGX_OK,意味着该模块认为满足访问要求,需要继续调用下一个模块查询是否满足要求
      r->phase_handler++;
      return NGX_AGAIN;
    }

    // 如果返回值不是NGX_OK,则调用ngx_http_finalize_request结束请求
  } else {  // 这里是配置中配置了satisfy any的情况
    // satisfy any不要求所有NGX_HTTP_ACCESS_PHASE阶段的模块都通过请求,只要有一个同意就可以放行

    // 返回NGX_OK,表示该模块认为有权限访问,因此phase_handler指向下一个handler
    // 并且access_code置为0
    if (rc == NGX_OK) {
      r->access_code = 0;

      if (r->headers_out.www_authenticate) {
        r->headers_out.www_authenticate->hash = 0;
      }

      r->phase_handler = ph->next;
      return NGX_AGAIN;
    }

    // NGX_HTTP_FORBIDDEN或者NGX_HTTP_UNAUTHORIZED表示该模块不同意访问
    if (rc == NGX_HTTP_FORBIDDEN || rc == NGX_HTTP_UNAUTHORIZED) {
      if (r->access_code != NGX_HTTP_UNAUTHORIZED) {
        r->access_code = rc;
      }

      r->phase_handler++;
      return NGX_AGAIN;
    }
  }

  /* rc == NGX_ERROR || rc == NGX_HTTP_...  */
  // 到这里是请求出错的情况,结束这次请求
  ngx_http_finalize_request(r, rc);
  return NGX_OK;
}              

NGX_HTTP_POST_ACCESS_PHASE(不可添加自定义HTTP模块)

本阶段处理上一阶段的处理结果,具体是根据ngx_http_request_t的access_code成员来进行处理。本阶段不允许添加自定义HTTP模块。

NGX_HTTP_TRY_FILES_PHASE(不可添加自定义HTTP模块)

本阶段的checker函数为ngx_http_core_try_files_phase,与nginx配置文件中的try_files配置指令相关。

NGX_HTTP_CONTENT_PHASE(可添加自定义HTTP模块)

处理HTTP请求内容的阶段,绝大部分用户自定义的HTTP模块工作在这一阶段。

与其他阶段不同的是,该阶段除了可以调用注册进来的HTTP模块的handler之外,还可以调用location配置中的content_handler,在存在location配置的content_handler的情况下,将优先调用这个函数处理请求。

// NGX_HTTP_CONTENT_PHASE阶段的处理函数
ngx_int_t
ngx_http_core_content_phase(ngx_http_request_t *r,
  ngx_http_phase_handler_t *ph)
{
  size_t     root;
  ngx_int_t  rc;
  ngx_str_t  path;

  if (r->content_handler) {
    // content_handler不为空意味着在NGX_HTTP_FIND_CONFIG_PHASE阶段中,
    // 匹配了URI请求的location内,是否有HTTP模块把处理方法设置到了
    // ngx_http_core_loc_conf_t结构体的handler成员中。

    // ngx_http_request_empty_handler是什么都不做的方法,设置为这个函数
    // 意味着再有写事件不做任何事情
    r->write_event_handler = ngx_http_request_empty_handler;
    // 将content_handler的返回值放入ngx_http_finalize_request中
    ngx_http_finalize_request(r, r->content_handler(r));
    return NGX_OK;
  }

  // 到了这里说明没有content_handler

  ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
    "content phase: %ui", r->phase_handler);

  rc = ph->handler(r);

  if (rc != NGX_DECLINED) {
    // 没有返回NGX_DECLINED意味着不再执行该阶段的其他handler
    ngx_http_finalize_request(r, rc);
    return NGX_OK;
  }

  /* rc == NGX_DECLINED */
  // 以下是返回NGX_DECLINED的情况
  ph++;

  if (ph->checker) {  // 只有在该阶段下一个handler存在的情况下才继续执行
    r->phase_handler++;
    return NGX_AGAIN;
  }

  // 以下是找不到该阶段handler的情况
  /* no content handler was found */

  if (r->uri.data[r->uri.len - 1] == '/') {
    // URI以/结尾,返回403
    if (ngx_http_map_uri_to_path(r, &path, &root, 0) != NULL) {
      ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
        "directory index of \"%s\" is forbidden", path.data);
    }

    ngx_http_finalize_request(r, NGX_HTTP_FORBIDDEN);
    return NGX_OK;
  }

  ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no handler found");
  // 返回404
  ngx_http_finalize_request(r, NGX_HTTP_NOT_FOUND);
  return NGX_OK;
}

NGX_HTTP_LOG_PHASE

记录访问日志。

参考资料