leevis.com icon indicating copy to clipboard operation
leevis.com copied to clipboard

ngx_http_rewrite_module代码分析

Open vislee opened this issue 8 years ago • 0 comments

概述

该模块是个复杂的模块,11个执行阶段其中3个阶段是为该模块设置的。rewrite模块强依赖pcre这个正则表达式库。

该模块提供了个简单的脚本执行引擎,可以在配置文件中写一些简单的脚本语言,该模块也是ngx_http_script.c的对外表现。 脚本引擎分为2个阶段,编译和执行。编译是在ngx启动时解析配置文件就开始的,执行是在请求处理过程中。

代码分析

通过ngx_http_rewrite_create_loc_conf函数分配一个下面的结构体保存配置。 其中,codes是保存脚本编译后的执行回调函数。 也就是说编译阶段是向codes数组中添加执行函数和变量,执行阶段是遍历codes数组调用函数。

typedef struct {
    ngx_array_t  *codes;        /* uintptr_t */

    ngx_uint_t    stack_size;

    ngx_flag_t    log;
    ngx_flag_t    uninitialized_variable_warn;
} ngx_http_rewrite_loc_conf_t;

通过ngx_http_rewrite_merge_loc_conf合并配置。其中codes并不会合并。

会用到的结构体:

typedef struct {
    u_char                     *ip;    // 指向回调函数
    u_char                     *pos;  // 指向buf
    ngx_http_variable_value_t  *sp;    // 数组,作为存放执行中间结果的栈

    ngx_str_t                   buf;  // 保存最终结果
    ngx_str_t                   line;

    /* 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;

typedef ngx_variable_value_t  ngx_http_variable_value_t;
typedef struct {
    unsigned    len:28;

    unsigned    valid:1;
    unsigned    no_cacheable:1;
    unsigned    not_found:1;
    unsigned    escape:1;

    u_char     *data;
} ngx_variable_value_t;

// 脚本执行结构体
typedef struct {
    // 脚本执行的结构体,第一个元素必需是ngx_http_script_code_pt类型的函数指针。
    // 因为codes数组元素是变长的,遍历数组是通过这个回调函数修改的。编程技巧。
    ngx_http_script_code_pt     code;
    ngx_http_regex_t           *regex;
    ngx_array_t                *lengths;
    uintptr_t                   size;
    uintptr_t                   status;
    uintptr_t                   next;

    unsigned                    test:1;
    unsigned                    negative_test:1;
    unsigned                    uri:1;
    unsigned                    args:1;

    /* add the r->args to the new arguments */
    unsigned                    add_args:1;

    unsigned                    redirect:1;
    unsigned                    break_cycle:1;

    ngx_str_t                   name;
} ngx_http_script_regex_code_t;

接下来会按照指令分析代码:

set

语法: set $name "${host}_liwq"

  • 启动阶段执行解析

    nginx会调用ngx_http_rewrite_set解析配置文件的set指令。

    1. 调用ngx_http_add_variable添加变量到cmcf->variables_keys。
    2. 调用ngx_http_get_variable_index添加变量到cmcf->variables。
    3. 调用ngx_http_rewrite_value添加计算变量的值的结构体到lcf->codes(其中codes是个数组,元素是变量)。
    4. 调用ngx_http_script_start_code函数添加一个赋值函数结构体(结构体包含了变量在cmcf->variables数组中的下标)到lcf->codes。
    5. 返回成功。
  • 运行阶段指令执行

    运行到11个阶段的NGX_HTTP_SERVER_REWRITE_PHASE或NGX_HTTP_REWRITE_PHASE阶段会调用ngx_http_rewrite_handler函数。

    1. 检测是否该执行。
    2. 分配ngx_http_script_engine_t 结构体e,以及e的sp指针为ngx_http_variable_value_t类型的数组。
    3. 让e的ip指向rlcf->codes->elts。即数组codes的首地址。
    4. e->ip不为空,则强制把首地址转为ngx_http_script_code_pt函数指针,并执行。 a. 执行ngx_http_script_value_code函数,把值保存到e的sp数组中。移动ip指针。 b. 执行ngx_http_script_set_var_code函数,把a步骤计算好的sp数组中的值保存到request->variables数组中。移动ip指针。 c. ip为空退出。正确则返回NGX_DECLINED,执行该阶段下一个handler。
  • 附:codes中添加的回调

    1. 常量字符串: a. ngx_http_script_value_code_t 结构体,ngx_http_script_value_code回调。 b. ngx_http_script_var_code_t 结构体, ngx_http_script_set_var_code回调,变量赋值函数。

    2. 常量+变量字符串: a. ngx_http_script_complex_value_code_t 结构体, ngx_http_script_complex_value_code回调,计算变量结果长度,分配字符串所需要的内存。 b. 字符串常量ngx_http_script_copy_code_t结构体,ngx_http_script_copy_code回调。 向lengths数组添加ngx_http_script_copy_code_t结构体,ngx_http_script_copy_len_code回调。 c. 变量ngx_http_script_var_code_t结构体, ngx_http_script_copy_var_code回调。 向lengths数组添加ngx_http_script_var_code_t结构体,ngx_http_script_copy_var_len_code回调。 d. ngx_http_script_var_code_t结构体, ngx_http_script_set_var_code 回调。

return

语法:return code [text]

  • 启动阶段执行解析

ngx会调用ngx_http_rewrite_return函数解析return指令。

向codes分配一个结构体ngx_http_script_return_code_t,其code赋值ngx_http_script_return_code回调函数。其status保存了指令的code。其text是对应了指令的text。

  • 运行阶段指令执行

  • codes中添加的函数

    1. ngx_http_script_return_code_t结构体,ngx_http_script_return_code回调。

相关代码:


typedef struct {
    ngx_http_script_code_pt     code;  // 回调函数
    uintptr_t                   status;
    ngx_http_complex_value_t    text;  // 脚本编译后的结果
} ngx_http_script_return_code_t;


void
ngx_http_script_return_code(ngx_http_script_engine_t *e)
{
    ngx_http_script_return_code_t  *code;

    code = (ngx_http_script_return_code_t *) e->ip;

    if (code->status < NGX_HTTP_BAD_REQUEST
        || code->text.value.len
        || code->text.lengths)
    {
        e->status = ngx_http_send_response(e->request, code->status, NULL,
                                           &code->text);
    } else {
        e->status = code->status;
    }

    e->ip = ngx_http_script_exit;
}

ngx_int_t
ngx_http_send_response(ngx_http_request_t *r, ngx_uint_t status,
    ngx_str_t *ct, ngx_http_complex_value_t *cv)
{
    ngx_int_t     rc;
    ngx_str_t     val;
    ngx_buf_t    *b;
    ngx_chain_t   out;

    // 丢弃请求的包体内容
    if (ngx_http_discard_request_body(r) != NGX_OK) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    r->headers_out.status = status;

    // 根据编译后的脚本获取结果
    if (ngx_http_complex_value(r, cv, &val) != NGX_OK) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    // 302等跳转
    if (status == NGX_HTTP_MOVED_PERMANENTLY
        || status == NGX_HTTP_MOVED_TEMPORARILY
        || status == NGX_HTTP_SEE_OTHER
        || status == NGX_HTTP_TEMPORARY_REDIRECT
        || status == NGX_HTTP_PERMANENT_REDIRECT)
    {
        ngx_http_clear_location(r);

        r->headers_out.location = ngx_list_push(&r->headers_out.headers);
        if (r->headers_out.location == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        r->headers_out.location->hash = 1;
        ngx_str_set(&r->headers_out.location->key, "Location");
        r->headers_out.location->value = val;

        return status;
    }

    r->headers_out.content_length_n = val.len;

    if (ct) {
        r->headers_out.content_type_len = ct->len;
        r->headers_out.content_type = *ct;

    } else {
        if (ngx_http_set_content_type(r) != NGX_OK) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }
    }

    // head方法 或 子请求 仅仅发送resp的头。
    if (r->method == NGX_HTTP_HEAD || (r != r->main && val.len == 0)) {
        return ngx_http_send_header(r);
    }

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

    b->pos = val.data;
    b->last = val.data + val.len;
    b->memory = val.len ? 1 : 0;
    b->last_buf = (r == r->main) ? 1 : 0;
    b->last_in_chain = 1;

    out.buf = b;
    out.next = NULL;

    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }

    return ngx_http_output_filter(r, &out);
}

if

语法: if (condition) { ... }

if是一个特殊的location,但还是location。还会挂在上一级的clcf->locations双链表上。

  • 启动阶段执行解析

    if指令会调用ngx_http_rewrite_if函数。

    1. 分配location所需的ctx,并赋值。调用ngx_http_add_location函数把location挂到上一级clcf->locations的链表上。
    2. 调用ngx_http_rewrite_if_condition函数处理条件,向lcf->codes数组中添加元素。即:$x = 'XXX' if 这写条件对应的函数和变量。变量添加的是ngx_http_script_var_code函数和变量的index,值添加的是ngx_http_script_value_code函数和对应的值,是否等于添加的是ngx_http_script_equal_code函数。
    3. 向lcf->codes 添加条件执行函数ngx_http_script_if_code。
    4. 调用ngx_conf_parse解析{}的内容,其cmd_type是NGX_HTTP_SIF_CONF或NGX_HTTP_LIF_CONF。而if location下的rewrite模块的codes是继承了上一级rewrite模块的。
    5. 解析{return 444} 参考return指令。
    6. if_code 的next是 if条件为false跳转的步长,也就是解析{}后codes的结尾。
  • 运行阶段指令执行

rewrite

语法: rewrite regex replacement [flag];

  • 启动阶段执行解析 rewrite指令的解析会调用ngx_http_rewrite函数。

    1. 在codes分配一个ngx_http_script_regex_code_t结构体,其回调函数code为ngx_http_script_regex_start_code,其regex为调用ngx_http_regex_compile函数编译好的正则表达式。
    2. 调用ngx_http_script_compile编译脚本。向codes添加ngx_http_script_copy_capture_code_t结构体,其回调函数code是ngx_http_script_copy_capture_code,其参数n是2占位符。同时向regex->lengths中添加ngx_http_script_copy_capture_code_t结构体,其回调函数code是ngx_http_script_copy_capture_len_code,其参数n是2占位符。regex->lengths还会添加个空指针。
    3. 向codes添加结构体ngx_http_script_regex_end_code_t,其回调函数code是ngx_http_script_regex_end_code。
    4. 配置了flag,也就是last为1,向codes添加一个空指针表示结束。
  • 运行阶段指令执行 rewrite阶段checker函数调用ngx_http_rewrite_handler函数。

    1. 执行ngx_http_script_regex_start_code函数,该函数先调用ngx_http_regex_exec函数匹配正则表达式。把捕获的值下标保存到请求结构体r->captures数组中。r->ncaptures为捕获结果最大坐标。r->captures_data为匹配字符串。同时分配保存结果需要的内存。
    2. 执行ngx_http_script_copy_capture_code函数,把上述步骤捕获的变量添加到结果内存上。
    3. 执行ngx_http_script_regex_end_code函数修改请求结构体的uri。
    4. 配置了flag,codes为空返回。handler返回NGX_DECLINED。

    返回到调用的checker函数ngx_http_core_rewrite_phase。NGX_HTTP_REWRITE_PHASE阶段只添加了一个回调函数。 执行到NGX_HTTP_POST_REWRITE_PHASE阶段的checker函数ngx_http_core_post_rewrite_phase。如果没有标识r->uri_changed(flag是break),则直接返回执行下一阶段的checker。如果标识了(flag是last)则跳回NGX_HTTP_FIND_CONFIG_PHASE阶段执行。

  • codes数组中添加的回调

    1. ngx_http_script_regex_code_t结构体, ngx_http_script_regex_start_code回调。
    2. 捕获变量结构体ngx_http_script_copy_capture_code_t, ngx_http_script_copy_capture_code回调函数。 在lengths添加ngx_http_script_copy_capture_code_t,回调函数是ngx_http_script_copy_capture_len_code。
    3. ngx_http_script_regex_end_code_t结构体,ngx_http_script_regex_end_code回调。

总结:

没有设置flag则会一直执行rewrite阶段所有的指令,包括return等。 如果flag设置了break,则会跳过rewrite阶段该模块剩余的指令,继续执行。 如果flag设置了是last,则会跳转到对应的find config阶段重新匹配新的location执行(rewrite配置在location内会重新跳转会find config阶段重新执行),rewrite配置在server内会改变url的location匹配。

  • 相关代码
static char *
ngx_http_rewrite(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_rewrite_loc_conf_t  *lcf = conf;

    ngx_str_t                         *value;
    ngx_uint_t                         last;
    ngx_regex_compile_t                rc;
    ngx_http_script_code_pt           *code;
    ngx_http_script_compile_t          sc;
    ngx_http_script_regex_code_t      *regex;
    ngx_http_script_regex_end_code_t  *regex_end;
    u_char                             errstr[NGX_MAX_CONF_ERRSTR];

    // 在codes分配一个元素,类型为ngx_http_script_regex_code_t
    regex = ngx_http_script_start_code(cf->pool, &lcf->codes,
                                       sizeof(ngx_http_script_regex_code_t));
    if (regex == NULL) {
        return NGX_CONF_ERROR;
    }

    ngx_memzero(regex, sizeof(ngx_http_script_regex_code_t));

    value = cf->args->elts;

    ngx_memzero(&rc, sizeof(ngx_regex_compile_t));

    // 正则表达式
    rc.pattern = value[1];
    rc.err.len = NGX_MAX_CONF_ERRSTR;
    rc.err.data = errstr;

    /* TODO: NGX_REGEX_CASELESS */

    // 编译正则表达式
    regex->regex = ngx_http_regex_compile(cf, &rc);
    if (regex->regex == NULL) {
        return NGX_CONF_ERROR;
    }

    // 脚本运行的回调
    regex->code = ngx_http_script_regex_start_code;
    regex->uri = 1;
    regex->name = value[1];

    if (value[2].data[value[2].len - 1] == '?') {

        /* the last "?" drops the original arguments */
        value[2].len--;

    } else {
        regex->add_args = 1;
    }

    last = 0;

    if (ngx_strncmp(value[2].data, "http://", sizeof("http://") - 1) == 0
        || ngx_strncmp(value[2].data, "https://", sizeof("https://") - 1) == 0
        || ngx_strncmp(value[2].data, "$scheme", sizeof("$scheme") - 1) == 0)
    {
        regex->status = NGX_HTTP_MOVED_TEMPORARILY;
        regex->redirect = 1;
        last = 1;
    }

    if (cf->args->nelts == 4) {
        if (ngx_strcmp(value[3].data, "last") == 0) {
            last = 1;

        } else if (ngx_strcmp(value[3].data, "break") == 0) {
            regex->break_cycle = 1;
            last = 1;

        } else if (ngx_strcmp(value[3].data, "redirect") == 0) {
            regex->status = NGX_HTTP_MOVED_TEMPORARILY;
            regex->redirect = 1;
            last = 1;

        } else if (ngx_strcmp(value[3].data, "permanent") == 0) {
            regex->status = NGX_HTTP_MOVED_PERMANENTLY;
            regex->redirect = 1;
            last = 1;

        } else {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "invalid parameter \"%V\"", &value[3]);
            return NGX_CONF_ERROR;
        }
    }

    ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));

    sc.cf = cf;
    sc.source = &value[2];
    sc.lengths = &regex->lengths;
    sc.values = &lcf->codes;
    sc.variables = ngx_http_script_variables_count(&value[2]);  // 带$的个数
    sc.main = regex;
    sc.complete_lengths = 1;
    sc.compile_args = !regex->redirect;  // 不是直接302等跳转的

    if (ngx_http_script_compile(&sc) != NGX_OK) {
        return NGX_CONF_ERROR;
    }

    regex = sc.main;

    regex->size = sc.size;  // 变量总的长度
    regex->args = sc.args;  // 参数添加了变量,就是 ?host=$host

    // 没有配置变量且没有重复使用占位符变量的
    if (sc.variables == 0 && !sc.dup_capture) {
        regex->lengths = NULL;
    }

    // 
    regex_end = ngx_http_script_add_code(lcf->codes,
                                      sizeof(ngx_http_script_regex_end_code_t),
                                      &regex);
    if (regex_end == NULL) {
        return NGX_CONF_ERROR;
    }

    regex_end->code = ngx_http_script_regex_end_code;
    regex_end->uri = regex->uri;
    regex_end->args = regex->args;    // 添加参数是变量
    regex_end->add_args = regex->add_args;  // 是否要添加参数
    regex_end->redirect = regex->redirect;  // 是否是直接302等跳转

    if (last) {
        // 结束标志,也就是配置了flag的
        code = ngx_http_script_add_code(lcf->codes, sizeof(uintptr_t), &regex);
        if (code == NULL) {
            return NGX_CONF_ERROR;
        }

        *code = NULL;
    }

    // 该条rewrite指令的添加到codes中所占的字节数。
    // 不匹配rewrite的正则表达式时会用到。
    regex->next = (u_char *) lcf->codes->elts + lcf->codes->nelts
                                              - (u_char *) regex;

    return NGX_CONF_OK;
}
  • 执行阶段的函数

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) {
        // 匹配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;
    }

    if (code->status) {
        // 302等跳转
        e->status = code->status;

        if (!code->redirect) {
            e->ip = ngx_http_script_exit;
            return;
        }
    }

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

rewrite 添加的回调函数

rewrite ^(/download/)(?<file>.*)\.mp3$ $1/mp3/${file}?aaa=bbb last;

添加file变量
regex->regex->variables[i].capture
regex->regex->variables[i].index    <----- script
v->get_handler = ngx_http_variable_not_found;


### lcf->codes

	// index 0
	ngx_http_script_regex_code_t:
		code = ngx_http_script_regex_start_code;

		(作用)
		执行正则匹配,分配结果buf结构


	// index 1(捕获位置变量)
	ngx_http_script_copy_capture_code_t
		code = ngx_http_script_copy_capture_code
		n = 2 * n

	// index 2(常量)
	ngx_http_script_copy_code_t + len + NULL
		ngx_http_script_copy_code
		len

	// index 3(变量)
	ngx_http_script_var_code_t
		ngx_http_script_copy_var_code
		index

	// index 4(参数)
	uintptr_t
		ngx_http_script_start_args_code

	// index 5()
	ngx_http_script_regex_end_code_t
		ngx_http_script_regex_end_code


	// index 5()
	NULL



### regex->lengths
	// index 0(捕获位置变量)
	ngx_http_script_copy_capture_code_t
	    code->code = (ngx_http_script_code_pt)
	                      ngx_http_script_copy_capture_len_code;
	    code->n = 2 * n;

	// index 1 (常量)
	ngx_http_script_copy_code_t
		ngx_http_script_copy_len_code
		len


	// index 2(变量)
	ngx_http_script_var_code_t
		ngx_http_script_var_code_t
		index

	// index 3(参数)
	uintptr_t
		ngx_http_script_mark_args_code

	// index 4
	NULL


vislee avatar Jul 23 '17 10:07 vislee