IT俱乐部 Nginx nginx slice模块的使用和源码分析小结

nginx slice模块的使用和源码分析小结

1. 为什么需要ngx_http_slice_module

顾名思义,nginx的slice模块的功能是在proxy代理的时候,会将代理到上游服务器的请求转换为若干个分片的子请求,最后将响应内容逐个返回给用户。

那么为什么要搞那么麻烦,将一个大文件切片成小的碎片文件来处理呢?原因有以下三点:

1. 大文件在整个下载的过程中持续的时间比较长,nginx和上游服务器(被代理的后端服务器)长时间建立连接,可能因为各种原因引起连接中断的概率大幅度上升。
2. 更重要的原因是大文件不利于cdn缓存。譬如著名的开源缓存服务器ats和squid,一般都需要等文件下载完全后才能将内容缓存到cache里面,如果用户下载了一半不下载了或者因为和上游服务器连接故障都会导致文件不能完整地被cache服务器下载下来而导致该文件不能被缓存,引起反复下载,降低内容的命中率。
3. 大文件在cdn架构中不容易平衡cache节点的负载,导致cache节点负载不平衡而影响用户体验。

而nginx slice模块的出现将大文件化整为零,很好地解决了以上这些问题。下面列出了一个CDN cache系统的典型架构,具体不做详述,后面可以另行撰文说明。

2. 配置指令

slice size;

  • 其中size是切片的大小,单位可以是K(千字节),M(兆字节),G(吉字节),单位大小写均可。

  • slice_size指令可以配置在”http”, “server”, “location” 块中定义。

    但是真正要启用slice功能,还要设置两条指令:

1
2
proxy_cache_key   $uri$is_args$args$slice_range;
proxy_set_header  Range $slice_range;

第一条指令表示如果使用nginx的自带缓存功能,那么nginx会以切片为单位进行缓存,那么缓存的时候需要对同一个文件的不同分片进行区分,所以需要将cache_key和每个切片的标识进行关联,这里使用了$slice_range变量。
第二条指令表示如果向上游服务器进行请求的时候,需要增加的HTTP Range头,该头的内容就是$slice_range变量的值。

附带说明一下:

  • $slice_range变量本身是由ngx_http_slice_module来定义并赋值的, 值的内容如:`bytes=0-1048575`。

3. 加载模块

在configure的时候需要添加ngx_http_slice_module来将其编译进来,

命令如下:

1
./configure --with-http_slice_module

 然后在nginx.conf 中添加以下配置,如:

1
2
3
4
5
6
7
8
location / {
    slice             1m;
    proxy_cache       cache;
    proxy_cache_key   $uri$is_args$args$slice_range;
    proxy_cache_valid 200 206 1h;
    proxy_set_header  Range $slice_range;
    proxy_pass        http://localhost:8000;
}

当然,如果不使用cache功能,只是单纯使用slice功能,那么proxy_cache、proxy_cache_key和proxy_cache_valid这些指令都不需要写了。

4. 源码分析

4.1 指令分析

1
2
3
4
5
6
7
8
9
10
11
static ngx_command_t  ngx_http_slice_filter_commands[] = {
 
    { ngx_string("slice"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_size_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_slice_loc_conf_t, size),
      NULL },
 
      ngx_null_command
};

从以上代码知道,slice指令可以在http server location块里面进行配置,一旦nginx发现slice指令,就调用ngx_conf_set_size_slot函数进行配置解析。ngx_conf_set_size_slot本身还是非常好理解的, 它是nginx在配置解析阶段用来解析大小的通用函数,解析的结果存放在ngx_http_slice_loc_conf_t的size字段中,如果size字段为0,标识不开启切片功能。当然需要说明一下,ngx_http_slice_loc_conf_t的实例会由ngx_http_slice_create_loc_conf来创建,并由ngx_http_slice_merge_loc_conf来合并,这方面的代码逻辑不再赘述。

4.2 模块初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
static ngx_http_module_t  ngx_http_slice_filter_module_ctx = {
    ngx_http_slice_add_variables,          /* preconfiguration */
    ngx_http_slice_init,                   /* postconfiguration */
 
    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */
 
    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */
 
    ngx_http_slice_create_loc_conf,        /* create location configuration */
    ngx_http_slice_merge_loc_conf          /* merge location configuration */
};

从以上代码知道,slice模块是作为一个nginx的filter模块参与切片工作的,同时ngx_http_slice_init是模块的初始化函数,而ngx_http_slice_add_variables是在preconfiguration阶段,也就是在初始化函数之前执行的函数,用来向nginx http框架添加$slice_range变量,供处理http请求的时候使用。

先看一下ngx_http_slice_add_variables函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static ngx_int_t
ngx_http_slice_add_variables(ngx_conf_t *cf)
{
    ngx_http_variable_t  *var;
 
    var = ngx_http_add_variable(cf, &ngx_http_slice_range_name, 0);
    if (var == NULL) {
        return NGX_ERROR;
    }
 
    var->get_handler = ngx_http_slice_range_variable;
 
    return NGX_OK;
}

以上代码非常简单,就是调用ngx_http_add_variable添加$slice_range变量,变量名存放在全局静态变量ngx_http_slice_range_name中,如下:

1
static ngx_str_t  ngx_http_slice_range_name = ngx_string("slice_range");

添加变量的时候还设置了获取该变量的回调函数为ngx_http_slice_range_variable,$slice_range变量只有get没有set,所以是一个只读类型的变量,其内容只能由slice模块内部进行更新,其他模块是不能对其进行更新操作的。

再看slice模块的初始化函数:

1
2
3
4
5
6
7
8
9
10
11
static ngx_int_t
ngx_http_slice_init(ngx_conf_t *cf)
{
    ngx_http_next_header_filter = ngx_http_top_header_filter;
    ngx_http_top_header_filter = ngx_http_slice_header_filter;
 
    ngx_http_next_body_filter = ngx_http_top_body_filter;
    ngx_http_top_body_filter = ngx_http_slice_body_filter;
 
    return NGX_OK;
}

显然,这就是典型的filter函数的初始化过程,就是将ngx_http_slice_header_filter和ngx_http_slice_body_filter分别以挂钩函数的方式挂入filter模块的两条调用链中,即header过滤器调用链和body过滤器调用链。

4.3 slice模块的上下文

在正式介绍请求处理逻辑之前,需要先了解一下ngx_http_slice_module模块的请求上下文,具体如下:

1
2
3
4
5
6
7
8
9
10
11
typedef struct {
    off_t                start;     /* 当前切片的起始偏移量 */
    off_t                end;       /* 请求内容的结束偏移量,不是指一个切片的结束偏移量,
                                       而是当前请求客户端需要的内容的结束偏移量 */
    ngx_str_t            range;     /* 存储$slice_range变量的字符串值 */
    ngx_str_t            etag;      /* 上游服务器响应的内容etag值,
                                       用来比对多个切片请求是否属于同一个切片 */
    unsigned             last:1;    /* 第一个切片请求是否已经完成了最后一个buf的处理 */
    unsigned             active:1;  /* 当前的切片请求响应处理过程执行中 */
    ngx_http_request_t  *sr;        /* 当前活跃中的子请求 */
} ngx_http_slice_ctx_t;

4.4 $slice_range字段值获取

为什么从$slice_range字段值的获取开始说的?因为,这个字段值的获取在nginx向上游服务器发起请求前,组织HTTP请求头的时候就会被调用了,执行顺序上面来说是放在执行http header和http body过滤函数的前面的。而且,获取这个字段值的时候,会创建slice模块的请求上下文ngx_http_slice_ctx_t, 另外需要明确的一点是,每发起一个向上游服务器的新的切片的请求前,都会重新获取这个字段值来组织新的请求头,所以在处理的过程中,这个变量的值是随着完成的切片的情况而需要不断更新的。
以下是字段值获取的回调函数的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
static ngx_int_t
ngx_http_slice_range_variable(ngx_http_request_t *r,
    ngx_http_variable_value_t *v, uintptr_t data)
{
    u_char                     *p;
    ngx_http_slice_ctx_t       *ctx;
    ngx_http_slice_loc_conf_t  *slcf;
     
    /* 获取当前请求本filter模块的上下文信息*/
    ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module);
     
    /* 如果为空表示上下文还没有创建,则需要创建一个新的上下文,
       这当然是在主请求上才会有这个情况,子请求不会出现这个情况除非当前filter是disable了,
       如果disable状态,当然返回当前$slice_range变量没有找到了 */
    if (ctx == NULL) {
        if (r != r->main || r->headers_out.status) {
            v->not_found = 1;
            return NGX_OK;
        }
         
        /* 获取本filter模块的配置信息 */
        slcf = ngx_http_get_module_loc_conf(r, ngx_http_slice_filter_module);
         
        /* 如果配置的切片size为0,表示切片功能禁用了,
           所以返回$slice_range变量找不到的错误信息 */
        if (slcf->size == 0) {
            v->not_found = 1;
            return NGX_OK;
        }
         
        /* 创建一个新的上下文并保存到当前request中 */
        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_slice_ctx_t));
        if (ctx == NULL) {
            return NGX_ERROR;
        }
 
        ngx_http_set_ctx(r, ctx, ngx_http_slice_filter_module);
         
        /* 分配一块内存,用于保存$slice_range的值*/
        p = ngx_pnalloc(r->pool, sizeof("bytes=-") - 1 + 2 * NGX_OFF_T_LEN);
        if (p == NULL) {
            return NGX_ERROR;
        }
         
        /* 设置本次需要向上游服务器发起的起始位置,详见下文*/
        ctx->start = slcf->size * (ngx_http_slice_get_start(r) / slcf->size);
 
        ctx->range.data = p;
        ctx->range.len = ngx_sprintf(p, "bytes=%O-%O", ctx->start,
                                     ctx->start + (off_t) slcf->size - 1)
                         - p;
    }
     
    /* 设置将返回的变量的信息 */
    v->data = ctx->range.data;
    v->valid = 1;           /* 标识变量可用标记 */
    v->not_found = 0;       /* 标识变量找到标记 */
    v->no_cacheable = 1;    /* 标识变量不可缓存标记 */
    v->len = ctx->range.len;
 
    return NGX_OK;
}

这里需要再稍微解释一下下面这个语句:

1
ctx->start = slcf->size * (ngx_http_slice_get_start(r) / slcf->size);

ngx_http_slice_get_start的函数调用是会去判断客户端的请求是否是range请求,如果不是range请求,那么很简单,就是从头获取文件的完整内容,所以必然是从0字节开始请求,否则需要解析当前客户端请求的HTTP Range头的信息,从而得到客户端希望的文件起始偏移量。得到的客户端实际希望的文件起始偏移量以后需要按照切片大小进行对齐后设置到ctx->start变量中,最后写入向上游服务器请求头中的HTTP Range字段。
那么为什么需要按照slice切片大小进行对齐向上游服务器请求呢?因为这样不会由于不同客户端请求的起始位置不同,导致产生大量的不同切片,引起缓存miss。对齐操作完全就是为了提升缓存的命中率。虽然在本次请求的时候向后端服务器多请求了一些内容,但是比起缓存hit带来的好处,还是非常非常值得的。

下面是ngx_http_slice_get_start的实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
static off_t
ngx_http_slice_get_start(ngx_http_request_t *r)
{
    off_t             start, cutoff, cutlim;
    u_char           *p;
    ngx_table_elt_t  *h;
     
    /* 不是range请求,直接返回0表示向上游服务器从头开始请求*/
    if (r->headers_in.if_range) {
        return 0;
    }
     
    /* 解析HTTP Range请求头,获取起始偏移量 */
    h = r->headers_in.range;
 
    if (h == NULL
        || h->value.len value.data, (u_char *) "bytes=", 6) != 0)
    {
        return 0;
    }
 
    p = h->value.data + 6;
 
    if (ngx_strchr(p, ',')) {
        return 0;
    }
 
    while (*p == ' ') { p++; }
 
    if (*p == '-') {
        return 0;
    }
 
    cutoff = NGX_MAX_OFF_T_VALUE / 10;
    cutlim = NGX_MAX_OFF_T_VALUE % 10;
 
    start = 0;
 
    while (*p >= '0' && *p = cutoff && (start > cutoff || *p - '0' > cutlim)) {
            return 0;
        }
 
        start = start * 10 + (*p++ - '0');
    }
 
    return start;
}

4.5 http header过滤处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
static ngx_int_t
ngx_http_slice_header_filter(ngx_http_request_t *r)
{
    off_t                            end;
    ngx_int_t                        rc;
    ngx_table_elt_t                 *h;
    ngx_http_slice_ctx_t            *ctx;
    ngx_http_slice_loc_conf_t       *slcf;
    ngx_http_slice_content_range_t   cr;
     
    /* 获取当前请求的slice模块的上下文, ctx上下文是在4.2节中描述的
       ngx_http_slice_range_variable中创建的,没有创建就会返回NULL,
       说明本次请求没有启用slice过滤模块,
       那么直接调用ngx_http_next_header_filter执行filter链中的后续模块的处理函数。
    */
    ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module);
    if (ctx == NULL) {
        return ngx_http_next_header_filter(r);
    }
    /*  调用本处理函数的时候,上游服务器发出的响应响应头已经被本nginx获取到了。
        如果响应的内容不是206,并且当前是第一个切片的请求
            (第一个切片请求只能是主请求发起,不是子请求),
        说明上游服务器不支持Range请求,则禁用切片功能。
        如果是子请求,而上游服务器已经响应了非206,那么第一个切片和后续的切片响应
        前后不一致,只能报错了。
    */
    if (r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT) {
        if (r == r->main) {
            ngx_http_set_ctx(r, NULL, ngx_http_slice_filter_module);
            return ngx_http_next_header_filter(r);
        }
 
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "unexpected status code %ui in slice response",
                      r->headers_out.status);
        return NGX_ERROR;
    }
     
    /* 检查主请求和自请求中响应内容的etag是否一致,如果不一致,则认为不是一个内容
       也只能报错了。
    */
    h = r->headers_out.etag;
 
    if (ctx->etag.len) {
        if (h == NULL
            || h->value.len != ctx->etag.len
            || ngx_strncmp(h->value.data, ctx->etag.data, ctx->etag.len)
               != 0)
        {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "etag mismatch in slice response");
            return NGX_ERROR;
        }
    }
     
    /* 在上下文中存储当前的etag信息,用于下一个子请求header处理的时候进行比对 */
    if (h) {
        ctx->etag = h->value; 
    }
     
    /* 分析上游服务器的响应头中的Content-Range头中的信息 */
    if (ngx_http_slice_parse_content_range(r, &cr) != NGX_OK) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "invalid range in slice response");
        return NGX_ERROR;
    }
     
    /* 如果Content-Range头中没有整个内容的长度信息,那么不能进行切片处理,只能报错 */
    if (cr.complete_length == -1) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "no complete length in slice response");
        return NGX_ERROR;
    }
 
    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "http slice response range: %O-%O/%O",
                   cr.start, cr.end, cr.complete_length);
                    
    /* 获取slice模块的配置信息 */
    slcf = ngx_http_get_module_loc_conf(r, ngx_http_slice_filter_module);
    /* 计算当前切片的结束偏移位置,也就是下一个切片的起始位置 */
    end = ngx_min(cr.start + (off_t) slcf->size, cr.complete_length);
     
    /* 判断希望请求的切片起止位置和实际上游服务器响应的起止位置是否一致 */
    if (cr.start != ctx->start || cr.end != end) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "unexpected range in slice response: %O-%O",
                      cr.start, cr.end);
        return NGX_ERROR;
    }
     
    ctx->start = end; /* 设置下一个切片的开始位置 */
    ctx->active = 1;  /* 设置当前的切片请求的响应进入活跃状态中 */
     
    /* 设置客户端响应的响应头信息,包括响应状态需要从206改成200,
       内容大小改成完整的大小而不是本次切片请求上游服务器返回的切片大小
    */
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.status_line.len = 0;
    r->headers_out.content_length_n = cr.complete_length;
    r->headers_out.content_offset = cr.start;
    r->headers_out.content_range->hash = 0;
    r->headers_out.content_range = NULL;
     
    /* 向客户端响应的时候需要清理掉Accept-Ranges头*/
    if (r->headers_out.accept_ranges) {
        r->headers_out.accept_ranges->hash = 0;
        r->headers_out.accept_ranges = NULL;
    }
 
    r->allow_ranges = 1;     /*设置允许ngx_http_range_filter_module执行Range处理*/
    r->subrequest_ranges = 1;/*本参数和allow_ranges的值一致的,可以忽略*/
    r->single_range = 1;     /*设置ngx_http_range_filter_module仅支持单个Range模式
                               不支持多Range模式 */
    /* 继续调用header filter链的下一个模块的处理函数,后续模块可能包括
       ngx_http_range_filter_module */
    rc = ngx_http_next_header_filter(r);
 
    if (r != r->main) {      /* 如果不相等,表示是子请求 */
        return rc;           /* 如果是子请求就直接返回,不执行后面的代码了 */*
    }
     
    /* 以下代码近在主请求中只会被执行1次,子请求中则不会进入到以下代码 */
    /* preserve_body字段的作用就是控制在转发请求时是否保留请求体。
    当preserve_body字段设置为1时,Nginx将会保留请求体数据,
    并将其传递给上游服务器。当preserve_body字段设置为0时(默认值),
    Nginx会在转发请求时丢弃请求体数据,只传递请求头部和其他元数据。
    */
    r->preserve_body = 1;
     
    /* 如果经过header filter的调用链处理后,
       ngx_http_range_filter_module处理了客户端发送来的Range请求,这个时候
       真正发送给客户端的状态是206响应,而不是前面设置的200。因为客户端的请求是Range请求,
       而当前处理的分片的起始范围在客户端请求要求的内容的起始偏移量前面,
       那么需要重新根据content_offset指定的偏移量调整向后端服务器请求的分片起始位置,
       而结束位置为客户端请求的结束位置偏移量。
    */
    if (r->headers_out.status == NGX_HTTP_PARTIAL_CONTENT) {
        if (ctx->start + (off_t) slcf->size headers_out.content_offset) {
            ctx->start = slcf->size
                         * (r->headers_out.content_offset / slcf->size);
        }
 
        ctx->end = r->headers_out.content_offset
                   + r->headers_out.content_length_n;
 
    } else {
        ctx->end = cr.complete_length;
    }
 
    return rc;
}

4.6 http body过滤处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
static ngx_int_t
ngx_http_slice_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_int_t                   rc;
    ngx_chain_t                *cl;
    ngx_http_slice_ctx_t       *ctx;
    ngx_http_slice_loc_conf_t  *slcf;
     
    /* 获取当前请求的slice模块的上下文,如果为空,说明本次请求没有启用slice过滤模块,
       那么直接调用ngx_http_next_header_filter执行filter链中的后续模块的处理函数。
    */
    ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module);
 
    if (ctx == NULL || r != r->main) { /* 如果是子请求,直接进入后续的调用链 */
        return ngx_http_next_body_filter(r, in);
    }
     
    /* 以下都是在主请求中处理
       如果last_buf此字段为1表明这是最后一个buf,但是对于整个请求来说只是一个切片的最后一块,
       不是整个请求的最后一个buf块,所以需要重新调整为0,并设置last_in_chain=1用于表明是本
       chain的最后一个buf块 */
    for (cl = in; cl; cl = cl->next) {
        if (cl->buf->last_buf) {          /* 当前子请求的最后一个buf并不是响应给客户端
                                             的最后一个buf,所以需要重新调整这个标记 */
            cl->buf->last_buf = 0;        /* 用于标识是否是最后一个缓冲区 */
            cl->buf->last_in_chain = 1;   /* 表示是否是链表中的最后一个缓冲区 */
            cl->buf->sync = 1;            /* 表示是否需要执行同步操作 */
            ctx->last = 1;                /* 第一个切片的最后一个buf以及获取到 */
        }
    }
     
    /* 调用body filter链后续filter模块的处理函数
       第一个切片请求是在主请求中发生的,这时in里面带有待发送到客户端的数据
       之后的切片请求是在子请求中发生的,这个时候子请求的调用链已经将数据发送到客户端了,
       到子请求把当前的切片发送完毕后,会通过发送一个in=NULL空的包重新激活主请求,
       这时主请求可以知道子请求已经完成了,从而可以根据需要开启一个新的子请求,
       或者结束请求的处理。
     */
    rc = ngx_http_next_body_filter(r, in);
 
    if (rc == NGX_ERROR || !ctx->last) {
        return rc;
    }
     
    /* 当前的子请求还没有处理完毕,返回nginx http框架,继续处理 */
    if (ctx->sr && !ctx->sr->done) {
        return rc;
    }
     
    /* ctx->active=1是在处理子请求头部信息即ngx_http_slice_header_filter函数中设置的
        如果=0, 则表示当前切片请求的响应还没有活跃状态,但是却需要发送body,
        应该是出了什么问题?
    */
    if (!ctx->active) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "missing slice response");
        return NGX_ERROR;
    }
     
    /* 所有内容已经全部响应给客户端,结束处理 */
    if (ctx->start >= ctx->end) {
        /* 因为内容已经发送完毕,上下文信息可以清理掉了
        ngx_http_set_ctx(r, NULL, ngx_http_slice_filter_module);
         
        /* 这里会通知nginx框架发送一个last_buf设置为1的ngx_buf_t缓冲区表示内容发送完毕 */
        ngx_http_send_special(r, NGX_HTTP_LAST);
        return rc;
    }
 
    /* buffered 字段是一个标志位,用于指示请求是否有未处理的请求体数据。
       当客户端发送一个带有请求体的 HTTP 请求时,
       请求体数据可能会被分成多个数据块(chunks)进行传输。
       buffered 字段用于跟踪这些请求体数据的处理状态。
       如果这个标记为1, 就暂时不能启动一个新的子请求 */
    if (r->buffered) {
        return rc;
    }
     
    /* 当前切片已全部响应给客户端,还有新的切片需要处理,开启一个新的子请求来获取新的切片 */
    if (ngx_http_subrequest(r, &r->uri, &r->args, &ctx->sr, NULL,
                            NGX_HTTP_SUBREQUEST_CLONE)
        != NGX_OK)
    {
        return NGX_ERROR;
    }
 
    ngx_http_set_ctx(ctx->sr, ctx, ngx_http_slice_filter_module);
 
    slcf = ngx_http_get_module_loc_conf(r, ngx_http_slice_filter_module);
     
    /* 设置下一个切片的$slice_range的字符串值*/
    ctx->range.len = ngx_sprintf(ctx->range.data, "bytes=%O-%O", ctx->start,
                                 ctx->start + (off_t) slcf->size - 1)
                     - ctx->range.data;
     
    /* 设置当前切片请求响应已经结束 */
    ctx->active = 0;
 
    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "http slice subrequest: "%V"", &ctx->range);
 
    return rc;
}

5 测试和验证

为了测试slice模块的效果,我们需要两个nginx服务,第一个nginx服务作为前端代理服务器,第二个nginx服务作为后端源服务器,为了简单起见,将这两个nginx服务都搭建在一台物理服务器上面。为了能够一目了然看清楚前端代理服务器确实向后端发送了切片请求,需要在后端nginx服务器的access日志上添加$http_range变量的输出。

下面先列出后端nginx的配置文件nginx.conf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
user  nobody;
worker_processes  1; 
 
error_log  logs/error.log;
pid        logs/nginx.pid;
 
 
events {
    worker_connections  1024;
}
 
 
http {
    include       mime.types;
    default_type  application/octet-stream;
 
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" '
                      '"Range: $http_range';
 
    access_log  logs/access.log  main;
    gzip off;
      
    server {
        listen 8888;
        location / {
             root html;
        }  
    }
}

这里需要特别注意的就是log_format 这个指令中添加了

1
'"Range: $http_range'

然后设置前端代理nginx的e配置文件nginx.conf: 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
user  nobody;
worker_processes  1; 
 
error_log  logs/error.log;
pid        logs/nginx.pid;
 
 
events {
    worker_connections  1024;
}
 
 
 
http {
    include       mime.types;
    default_type  application/octet-stream;
 
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
 
    access_log  logs/access.log  main;
     
    server {
        listen 9080;
         
        location / {
            slice 1m;
            proxy_set_header Range $slice_range;
            proxy_buffering off;
            proxy_pass http://127.0.0.1:8888;
        }
    }
}

主要需要注意的是location / { } 中的设置。
然后启动两个nginx服务,通过curl来验证,

测试用例1, 完整文件请求,如:

1
curl "http://127.0.0.1:9080/a.pdf" > /dev/null

查看第二个nginx的access.log日志,如下: 

1
2
3
4
5
6
7
8
9
10
11
12
13
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=0-1048575
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=1048576-2097151
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=2097152-3145727
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=3145728-4194303
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=4194304-5242879
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=5242880-6291455
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=6291456-7340031
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=7340032-8388607
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=8388608-9437183
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=9437184-10485759
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=10485760-11534335
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=11534336-12582911
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1015274 "-" "curl/7.81.0" "-" "Range: bytes=12582912-13631487

一个客户端的http请求在第二个后端源nginx上收到了若干个响应为206的HTTP请求,表明前端nginx的切片功能已经正常开启了。

测试用例2, Range请求,如:

1
curl "http://127.0.0.1:9080/a.pdf" -H"Range: bytes=1048577-5788888" > /dev/null

查看第二个nginx的access.log日志,如下: 

1
2
3
4
5
127.0.0.1 - - [04/Feb/2024:16:30:29 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=1048576-2097151
127.0.0.1 - - [04/Feb/2024:16:30:29 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=2097152-3145727
127.0.0.1 - - [04/Feb/2024:16:30:29 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=3145728-4194303
127.0.0.1 - - [04/Feb/2024:16:30:29 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=4194304-5242879
127.0.0.1 - - [04/Feb/2024:16:30:29 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=5242880-6291455

这次可以看到,第二个nginx收到的第一个请求的Range范围是1048576-2097151,正好对应第二个切片的范围,虽然我们请求要求的起始位置是1048577;同时,最后一个切片请求的结束位置是6291455,而这个正好是第五个切片的最后一个字节的偏移量。这样子验证了nginx slice功能的切片功能是按照切片对齐的方式向上游服务器发送请求的。

到此这篇关于nginx slice模块的使用和源码分析小结的文章就介绍到这了,更多相关nginx slice模块内容请搜索IT俱乐部以前的文章或继续浏览下面的相关文章希望大家以后多多支持IT俱乐部!

本文收集自网络,不代表IT俱乐部立场,转载请注明出处。https://www.2it.club/server/nginx/10201.html
上一篇
下一篇
联系我们

联系我们

在线咨询: QQ交谈

邮箱: 1120393934@qq.com

工作时间:周一至周五,9:00-17:30,节假日休息

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部