rewrite 规则的处理 -- ngx_http_rewrite_handler

2015-11-20 13:45:51   最后更新: 2015-11-20 13:45:51   访问数量:765




nginx 提供了十分强大的 rewrite 功能

nginx rewrite 规则的配置 中,我们介绍了 nginx rewrite 规则的配制方法

下面,我们从源码的角度看一下 nginx 是怎么处理 rewrite 的

 

HTTP 请求处理的 11 个阶段 -- ngx_http_handler 中,我们介绍了 nginx 处理 HTTP 请求的 11 个阶段

rewrite 规则的处理就在 nginx HTTP 解析开始的几个阶段中

 

  1. NGX_HTTP_SERVER_REWRITE_PHASE ngx_http_core_rewrite_phase
  2. NGX_HTTP_FIND_CONFIG_PHASE ngx_http_core_find_config_phase
  3. NGX_HTTP_REWRITE_PHASE ngx_http_core_rewrite_phase
  4. NGX_HTTP_POST_REWRITE_PHASE ngx_http_core_post_rewrite_phase

 

虽然 rewrite 的解析分成了 NGX_HTTP_SERVER_REWRITE_PHASE 和 NGX_HTTP_REWRITE_PHASE 两个阶段进行,事实上,他们调用的 checker 是相同的:ngx_http_core_rewrite_phase,而在 checker 中同样调用了相同的 handler:ngx_http_rewrite_handler,这个回调函数是 ngx_http_rewrite_module 实现的

 

// static ngx_int_t ngx_http_rewrite_handler(ngx_http_request_t *r) // URI rewrite 回调函数 {{{ static ngx_int_t ngx_http_rewrite_handler(ngx_http_request_t *r) { ngx_int_t index; ngx_http_script_code_pt code; ngx_http_script_engine_t *e; ngx_http_core_srv_conf_t *cscf; ngx_http_core_main_conf_t *cmcf; ngx_http_rewrite_loc_conf_t *rlcf; cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); index = cmcf->phase_engine.location_rewrite_index; if (r->phase_handler == index && r->loc_conf == cscf->ctx->loc_conf) { /* skipping location rewrite phase for server null location */ return NGX_DECLINED; } rlcf = ngx_http_get_module_loc_conf(r, ngx_http_rewrite_module); if (rlcf->codes == NULL) { return NGX_DECLINED; } // 创建脚本驱动结构域 e = ngx_pcalloc(r->pool, sizeof(ngx_http_script_engine_t)); if (e == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } // 创建变量值存储结构 e->sp = ngx_pcalloc(r->pool, rlcf->stack_size * sizeof(ngx_http_variable_value_t)); if (e->sp == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } e->ip = rlcf->codes->elts; e->request = r; e->quote = 1; e->log = rlcf->log; e->status = NGX_DECLINED; while (*(uintptr_t *) e->ip) { code = *(ngx_http_script_code_pt *) e->ip; // ngx_http_script_regex_start_code // 执行正则表达式 // ngx_http_script_copy_code // 将配置信息存入脚本结构缓冲区 // ngx_http_script_copy_capture_code // 复制跳转目标到脚本结构缓冲区 // ngx_http_script_regex_end_code // 收尾工作 code(e); } if (e->status < NGX_HTTP_BAD_REQUEST) { return e->status; } if (r->err_status == 0) { return e->status; } return r->err_status; } // }}}

 

 

rewrite 规则的解析事实上是 nginx 自己实现的一个脚本解析器,用户配置的 rewrite 规则就是这个脚本,而 URI 或某些变量就是这个脚本的参数

这个脚本的描述结构就是 ngx_http_script_engine_t,ngx_http_rewrite_handler 函数中做的事情就是为脚本结构 ngx_http_script_engine_t 分配空间并初始化,同时执行配置结构中的脚本解析函数

 

// struct ngx_http_script_engine_t // 脚本引擎描述结构 {{{ typedef struct { u_char *ip; // 脚本描述结构数组 u_char *pos; // 指向配置信息待插入位置 ngx_http_variable_value_t *sp; // 变量存储结构 ngx_str_t buf; // 配置信息(rewrite 目的地)缓冲 ngx_str_t line; // rewrite 判断依据信息 /* the start of the rewritten arguments */ u_char *args; unsigned flushed:1; unsigned skip:1; unsigned quote:1; unsigned is_args:1; unsigned log:1; ngx_int_t status; // 状态 ngx_http_request_t *request; // 对应的请求 } ngx_http_script_engine_t; // }}}

 

 

脚本的解析是通过一组函数进行的,而 ngx_http_script_engine_t 的 ip 字段存储了所有的脚本描述结构组成的数组,巧妙的地方在于,nginx 约定,所有的脚本描述结构的第一个字段必须是脚本执行的回调函数,这也就意味着每个脚本描述结构的首地址都是相关操作函数的首地址,只要获取指向改描述结构的指针,通过强制类型转换就可以直接调用对应的操作函数,在 ngx_http_rewrite_handler 中正是这么做的

而解析的结果信息就存储在 buf 字段中,由 pos 字段指向 buf 的待使用位置,而传入的 URI 或依据变量被存储在 line 字段作为脚本的参数,而因为 rewrite 返回的状态码则存在 status 字段里

 

// void ngx_http_script_regex_start_code(ngx_http_script_engine_t *e) // 脚本执行 {{{ void ngx_http_script_regex_start_code(ngx_http_script_engine_t *e) { size_t len; ngx_int_t rc; ngx_uint_t n; ngx_http_request_t *r; ngx_http_script_engine_t le; ngx_http_script_len_code_pt lcode; ngx_http_script_regex_code_t *code; code = (ngx_http_script_regex_code_t *) e->ip; r = e->request; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http script regex: \"%V\"", &code->name); if (code->uri) { e->line = r->uri; } else { e->sp--; e->line.len = e->sp->len; e->line.data = e->sp->data; } // 解析正则表达式,执行脚本 rc = ngx_http_regex_exec(r, code->regex, &e->line); // 没有命中 if (rc == NGX_DECLINED) { if (e->log || (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP)) { ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0, "\"%V\" does not match \"%V\"", &code->name, &e->line); } r->ncaptures = 0; if (code->test) { if (code->negative_test) { e->sp->len = 1; e->sp->data = (u_char *) "1"; } else { e->sp->len = 0; e->sp->data = (u_char *) ""; } e->sp++; e->ip += sizeof(ngx_http_script_regex_code_t); return; } // 适配下一规则 e->ip += code->next; return; } // 执行出错 if (rc == NGX_ERROR) { e->ip = ngx_http_script_exit; e->status = NGX_HTTP_INTERNAL_SERVER_ERROR; return; } if (e->log || (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP)) { ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0, "\"%V\" matches \"%V\"", &code->name, &e->line); } if (code->test) { if (code->negative_test) { e->sp->len = 0; e->sp->data = (u_char *) ""; } else { e->sp->len = 1; e->sp->data = (u_char *) "1"; } e->sp++; e->ip += sizeof(ngx_http_script_regex_code_t); return; } // 301、302 跳转 if (code->status) { e->status = code->status; if (!code->redirect) { e->ip = ngx_http_script_exit; return; } } // 使用 URI 匹配 if (code->uri) { r->internal = 1; r->valid_unparsed_uri = 0; if (code->break_cycle) { r->valid_location = 0; r->uri_changed = 0; } else { r->uri_changed = 1; } } if (code->lengths == NULL) { e->buf.len = code->size; if (code->uri) { if (r->ncaptures && (r->quoted_uri || r->plus_in_uri)) { e->buf.len += 2 * ngx_escape_uri(NULL, r->uri.data, r->uri.len, NGX_ESCAPE_ARGS); } } for (n = 2; n < r->ncaptures; n += 2) { e->buf.len += r->captures[n + 1] - r->captures[n]; } } else { ngx_memzero(&le, sizeof(ngx_http_script_engine_t)); le.ip = code->lengths->elts; le.line = e->line; le.request = r; le.quote = code->redirect; len = 0; while (*(uintptr_t *) le.ip) { lcode = *(ngx_http_script_len_code_pt *) le.ip; len += lcode(&le); } e->buf.len = len; } if (code->add_args && r->args.len) { e->buf.len += r->args.len + 1; } // 为脚本缓冲区开辟空间 e->buf.data = ngx_pnalloc(r->pool, e->buf.len); if (e->buf.data == NULL) { e->ip = ngx_http_script_exit; e->status = NGX_HTTP_INTERNAL_SERVER_ERROR; return; } e->quote = code->redirect; e->pos = e->buf.data; e->ip += sizeof(ngx_http_script_regex_code_t); } // }}}

 

 

这个函数中,对正则表达式脚本进行了解析,并初始化了 ngx_http_script_regex_code_t 结构

// struct ngx_http_script_regex_code_t // http 脚本描述结构 {{{ typedef struct { ngx_http_script_code_pt code; // 处理回调函数 ngx_http_regex_t *regex; // 正则表达式描述结构 ngx_array_t *lengths; uintptr_t size; uintptr_t status; uintptr_t next; uintptr_t test:1; uintptr_t negative_test:1; uintptr_t uri:1; // 是否针对请求 URI 判断 uintptr_t args:1; /* add the r->args to the new arguments */ uintptr_t add_args:1; // 执行后是否重定向,对应 rewrite 配置的 redirect(302)、permant(301) uintptr_t redirect:1; // 执行后是否退出,对应 rewrite 配置的 break uintptr_t break_cycle:1; ngx_str_t name; // 脚本代码 } ngx_http_script_regex_code_t; // }}}

 

 

通过函数的执行,我们可以知道 rewrite 后是否进行重定向等属性,如果匹配成功,则进行后续步骤

  1. ngx_http_script_copy_code -- 保存脚本长度信息
  2. ngx_http_script_copy_capture_code -- 保存跳转目标到 rewrite 描述结构缓存中
  3. ngx_http_script_regex_end_code -- 执行 URL decode 等收尾工作

 

随后,如果需要重定向,则 ngx_http_rewrite_handler 会返回 NGX_OK,进而执行 ngx_http_finalize_request 返回重定向信息,而不会进入后续流程

否则程序会继续执行,在 NGX_HTTP_POST_REWRITE_PHASE 阶段检测跳转次数,如果跳转次数多于十次,则返回 500 Internal Server Error,否则继续执行,进入正常的 HTTP 解析流程,返回数据

 






技术帖      web      c语言      龙潭书斋      源代码      nginx      server      sourcecode      webserver      source      code      rewrite     


京ICP备15018585号