slice的作用:
CDN系统中,向父层回源时,如果文件过大,使用slice可以让用户快速得到响应。
问题:
对于一个用户的请求,使用slice对文件进行分片,如何保证可以完整的把整个文件返回给用户?
通常配置:
location / { slice 1m; proxy_cache cache; proxy_cache_key $uri$is_args$args$slice_range; proxy_set_header Range $slice_range; proxy_cache_valid 200 206 1h; proxy_pass http://localhost:8000; }
该模块流程以及实现
slice是一个filter,按照nginx的phase划分,是处在content phase。
slice命令可以设置每个分片的大小,会设置slice命令所在的当前位置的loc_conf中size
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; //slice的处理函数
return NGX_OK;
}
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;
ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module);
//如果没有绑定上下文,则进行分配
if (ctx == NULL) {
if (r != r->main || r->headers_out.status) {
v->not_found = 1;
return NGX_OK;
}
//获取slice命令分配的分片大小
slcf = ngx_http_get_module_loc_conf(r, ngx_http_slice_filter_module);
if (slcf->size == 0) {
v->not_found = 1;
return NGX_OK;
}
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);
p = ngx_pnalloc(r->pool, sizeof("bytes=-") - 1 + 2 * NGX_OFF_T_LEN);
if (p == NULL) {
return NGX_ERROR;
}
//获取range_start大小,考虑到用户本身携带了range
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;
}
封装slice_range变量
v->data = ctx->range.data;
v->valid = 1;
v->not_found = 0;
v->no_cacheable = 1;
v->len = ctx->range.len;
return NGX_OK;
}
static ngx_int_t
ngx_http_slice_init(ngx_conf_t *cf)
{
ngx_http_next_header_filter = ngx_http_top_header_filter;
//header_filter的处理函数为:ngx_http_slice_header_filter
ngx_http_top_header_filter = ngx_http_slice_header_filter;
ngx_http_next_body_filter = ngx_http_top_body_filter;
//body_filter的处理函数为:ngx_http_slice_body_filter
ngx_http_top_body_filter = ngx_http_slice_body_filter;
return NGX_OK;
}
ngx_http_slice_header_filter处理流程:
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的,直接走到下一个header_filter
ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module);
if (ctx == NULL) {
return ngx_http_next_header_filter(r);
}
//如果配置了slice,但是返回又不是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;
}
h = r->headers_out.etag;
检查etag
当没有的时候把头携带的etag赋值给ctx->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;
}
}
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;
}
complete_length 代表的是整个文件的长度
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);
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;
}
更新start,赋值为当前header的content_range
ctx->start = end;
//把状态码设置为200
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.status_line.len = 0;
把content_length_n 设置为文件的长度
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;
//修改range相关
r->allow_ranges = 1;
r->subrequest_ranges = 1;
r->single_range = 1;
rc = ngx_http_next_header_filter(r);
//如果不是子请求,则直接返回
if (r != r->main) {
return rc;
}
//后面应该还有对r->headers_out.status的赋值?在哪里?
//主要是设置ctx->start ctx->end
if (r->headers_out.status == NGX_HTTP_PARTIAL_CONTENT) {
if (ctx->start + (off_t) slcf->size <= r->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;
}
static ngx_int_t
ngx_http_slice_parse_content_range(ngx_http_request_t *r, ngx_http_slice_content_range_t *cr)
{
off_t start, end, complete_length, cutoff, cutlim;
u_char *p;
ngx_table_elt_t *h;
h = r->headers_out.content_range;
错误检查
if (h == NULL
|| h->value.len < 7
|| ngx_strncmp(h->value.data, "bytes ", 6) != 0)
{
return NGX_ERROR;
}
//接下来就是分析30-200/500,这种格式
分别把30 200 500取出来
p = h->value.data + 6;
cutoff = NGX_MAX_OFF_T_VALUE / 10;
cutlim = NGX_MAX_OFF_T_VALUE % 10;
start = 0;
end = 0;
complete_length = 0;
while (*p == ' ') { p++; }
if (*p < '0' || *p > '9') {
return NGX_ERROR;
}
while (*p >= '0' && *p <= '9') {
if (start >= cutoff && (start > cutoff || *p - '0' > cutlim)) {
return NGX_ERROR;
}
start = start * 10 + *p++ - '0';
}
while (*p == ' ') { p++; }
if (*p++ != '-') {
return NGX_ERROR;
}
while (*p == ' ') { p++; }
if (*p < '0' || *p > '9') {
return NGX_ERROR;
}
while (*p >= '0' && *p <= '9') {
if (end >= cutoff && (end > cutoff || *p - '0' > cutlim)) {
return NGX_ERROR;
}
end = end * 10 + *p++ - '0';
}
end++;
while (*p == ' ') { p++; }
if (*p++ != '/') {
return NGX_ERROR;
}
while (*p == ' ') { p++; }
if (*p != '*') {
if (*p < '0' || *p > '9') {
return NGX_ERROR;
}
while (*p >= '0' && *p <= '9') {
if (complete_length >= cutoff
&& (complete_length > cutoff || *p - '0' > cutlim))
{
return NGX_ERROR;
}
complete_length = complete_length * 10 + *p++ - '0';
}
} else {
complete_length = -1;
p++;
}
while (*p == ' ') { p++; }
if (*p != '\0') {
return NGX_ERROR;
}
cr->start = start;
cr->end = end;
cr->complete_length = complete_length;
return NGX_OK;
}
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_request_t *sr;
ngx_http_slice_ctx_t *ctx;
ngx_http_slice_loc_conf_t *slcf;
ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module);
子请求直接走下一个filter
if (ctx == NULL || r != r->main) {
return ngx_http_next_body_filter(r, in);
}
如果是下一个buf,则做标记
for (cl = in; cl; cl = cl->next) {
if (cl->buf->last_buf) {
cl->buf->last_buf = 0;
cl->buf->last_in_chain = 1;
cl->buf->sync = 1;
ctx->last = 1;
}
}
rc = ngx_http_next_body_filter(r, in);
if (rc == NGX_ERROR || !ctx->last) {
return rc;
}
if (ctx->start >= ctx->end) {
ngx_http_set_ctx(r, NULL, ngx_http_slice_filter_module);
ngx_http_send_special(r, NGX_HTTP_LAST);
return rc;
}
if (r->buffered) {
return rc;
}
如果是当前请求的最后一个buf,则创建子请求
if (ngx_http_subrequest(r, &r->uri, &r->args, &sr, NULL, 0) != NGX_OK) {
return NGX_ERROR;
}
ngx_http_set_ctx(sr, ctx, ngx_http_slice_filter_module);
slcf = ngx_http_get_module_loc_conf(r, ngx_http_slice_filter_module);
ctx->range.len = ngx_sprintf(ctx->range.data, "bytes=%O-%O", ctx->start,
ctx->start + (off_t) slcf->size - 1)
- ctx->range.data;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http slice subrequest: \"%V\"", &ctx->range);
return rc;
}
执行点:
1,派生多个子请求,头部只能发一次,是通过header_filter来控制的,r里面保存了header_sent,如果已经把头发送过了则把对应的标志置为1
2,派生子请求的流程
proxy解析变量slice_range时会根据设置的slice命令来组装请求头,在向客户端发送请求结果时,如果是当前的最后一个buffer,则会创建子请求,
创建的子请求是挂载到主请求的post队列里面的,而且可以看到在slice_body处理函数里面,只有主请求才能再次创建子请求进行下一部分内容的
读取,那主请求是如何再次唤醒?在子请求结束时,子请求会把主请求自己加入到主请求自己的post队列里面,这样在执行
ngx_http_run_posted_requests函数时,就会再次执行自己的slice_filter模块,
具体代码为: if (ngx_http_post_request(pr, NULL) != NGX_OK) {
r->main->count++;
ngx_http_terminate_request(r, 0);
return;
}
3,如果主请求长时间不发包或者收包,没有被断开的原因暂时还没有找到。
4,后续再接着补充