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

文章目录

  • 1. 为什么需要ngx_http_slice_module
  • 2. 配置指令
  • 3. 加载模块
  • 4. 源码分析
    • 4.1 指令分析
    • 4.2 模块初始化
    • 4.3 slice模块的上下文
    • 4.2 $slice_range字段值获取
    • 4.3 http header过滤处理
    • 4.4 http body过滤处理
    • 5 测试和验证

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功能,还要设置两条指令:

    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来将其编译进来,

命令如下:

./configure --with-http_slice_module
然后在nginx.conf 中添加以下配置,如:
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 指令分析

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 模块初始化

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函数:
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中,如下:
static ngx_str_t  ngx_http_slice_range_name = ngx_string("slice_range");
添加变量的时候还设置了获取该变量的回调函数为ngx_http_slice_range_variable,$slice_range变量只有get没有set,所以是一个只读类型的变量,其内容只能由slice模块内部进行更新,其他模块是不能对其进行更新操作的。再看slice模块的初始化函数:
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模块的请求上下文,具体如下:
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.2 $slice_range字段值获取

为什么从$slice_range字段值的获取开始说的?因为,这个字段值的获取在nginx向上游服务器发起请求前,组织HTTP请求头的时候就会被调用了,执行顺序上面来说是放在执行http header和http body过滤函数的前面的。而且,获取这个字段值的时候,会创建slice模块的请求上下文ngx_http_slice_ctx_t, 另外需要明确的一点是,每发起一个向上游服务器的新的切片的请求前,都会重新获取这个字段值来组织新的请求头,所以在处理的过程中,这个变量的值是随着完成的切片的情况而需要不断更新的。
以下是字段值获取的回调函数的实现:
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;
}
这里需要再稍微解释一下下面这个语句:
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的实现代码:
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 < 7|| ngx_strncasecmp(h->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 <= '9') {if (start >= cutoff && (start > cutoff || *p - '0' > cutlim)) {return 0;}start = start * 10 + (*p++ - '0');}return start;
}

4.3 http header过滤处理

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

4.4 http body过滤处理

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:
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 这个指令中添加了
'"Range: $http_range'
然后设置前端代理nginx的e配置文件nginx.conf:
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, 完整文件请求,如:

curl "http://127.0.0.1:9080/a.pdf" > /dev/null
查看第二个nginx的access.log日志,如下:
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请求,如:

curl "http://127.0.0.1:9080/a.pdf" -H"Range: bytes=1048577-5788888" > /dev/null
查看第二个nginx的access.log日志,如下:
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功能的切片功能是按照切片对齐的方式向上游服务器发送请求的。![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/6720d620740140a0b2c4145d52a5fb9f.png#pic_center)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/668527.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

程序员为什么不喜欢关电脑,这回答很霸道!

在大家的生活中&#xff0c;经常会发现这样一个现象&#xff1a;程序员经常不关电脑。 至于程序员不关电脑的原因&#xff0c;众说纷纭。 其中这样的一个程序员&#xff0c;他的回答很霸道&#xff1a; “因为我是程序员&#xff0c;我有权选择不关电脑。我需要在任何时候都能够…

C++一维数组

个人主页&#xff1a;PingdiGuo_guo 收录专栏&#xff1a;C干货专栏 铁汁们大家好呀&#xff0c;我是PingdiGuo_guo&#xff0c;今天我们来学习一下数组&#xff08;一维&#xff09;。 文章目录 1.数组的概念与思想 2.为什么要使用数组 3.数组的特性 4.数组的操作 1.定义…

0 代码自动化测试:RF 框架实现企业级 UI 自动化测试

前言 现在大家去找工作&#xff0c;反馈回来的基本上自动化测试都是刚需&#xff01;没有自动化测试技能&#xff0c;纯手工测试基本没有什么市场。 但是很多人怕代码&#xff0c;觉得自动化测试就需要代码&#xff01;代码学习起来很难&#xff01; 当然代码学习不难&#xf…

优思学院|精益生产-改变制造业的革命性理念

在今日这个变幻莫测、竞争如潮的市场环境中&#xff0c;企业如同海上的帆船&#xff0c;面临着狂风巨浪的考验。在这样的大背景之下&#xff0c;精益生产&#xff08;Lean Production&#xff09;这一理念&#xff0c;宛如一盏明灯&#xff0c;指引着无数企业穿越迷雾&#xff…

安科瑞消防设备电源监控系统在杭后旗医院项目的设计与应用

摘要&#xff1a;本文简述了消防设备电源的组成原理&#xff0c;分析了消防设备电源监控系统在应用中的设计依据和相关规范。通过安科瑞消防设备电源监控系统在杭后旗医院项目的实例介绍&#xff0c;阐述了消防设备电源功能的实现及其重要意义。 关键词&#xff1a;消防设备电…

【jenkins】主从机制及添加Slave节点操作

一、master-slave 日常构建Jenkins任务中&#xff0c;会经常出现下面的情况&#xff1a; 自动化测试需要消耗大量的 CPU 和内存资源&#xff0c;如果服务器上还有其他的服务&#xff0c;可能会造成卡顿或者宕机这样的情况&#xff1b; Jenkins 平台上除了这个项目&#xff0c…

【Linux】解决:为什么重复创建同一个【进程pid会变化,而ppid父进程id不变?】

前言 大家好吖&#xff0c;欢迎来到 YY 滴Linux 系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过Linux的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的…

开源软件在技术革新和行业变革中的作用

引言&#xff1a; 在数字化浪潮推动下&#xff0c;开源软件以其独特的魅力重构了软件开发的生态系统&#xff0c;成为技术创新和行业变革的催化剂。它通过低成本、高协作性、极致透明度的特征&#xff0c;成为企业和个人的首选。本文将深度探讨开源软件的影响力&#xff0c;展…

【C++刷题】二叉树的深搜

二叉树的深搜 一、计算布尔二叉树的值1、题目描述2、代码3、解析 二、求根节点到叶节点数字之和1、题目描述2、代码3、解析 三、二叉树剪枝1、题目描述2、代码3、解析 四、验证二叉搜索树1、题目描述2、代码3、解析 五、二叉搜索树中第K小的元素1、题目描述2、代码3、解析 六、…

16.docker删除redis缓存数据、redis常用基本命令

1.进入redis容器内部 &#xff08;1&#xff09;筛选过滤出redis容器 docker ps | grep "redis"&#xff08;2&#xff09;进入redis容器 #说明&#xff1a;d24为redis容器iddocker exec -it d24 /bin/bash2.登陆redis (1) 进入redis命令行界面 redis-cli说明&a…

重写Sylar基于协程的服务器(7、TcpServer HttpServer的设计与实现)

重写Sylar基于协程的服务器&#xff08;7、TcpServer & HttpServer的设计与实现&#xff09; 重写Sylar基于协程的服务器系列&#xff1a; 重写Sylar基于协程的服务器&#xff08;0、搭建开发环境以及项目框架 || 下载编译简化版Sylar&#xff09; 重写Sylar基于协程的服务…

CAN通信----(创芯科技)CAN分析仪----转CANTest使用

点击进入官方链接进行下载创芯科技 CAN分析仪资料包&#xff1a; 创芯科技的官网&#xff1a;https://m.zhcxgd.com/ 我使用的是至尊版红色带OBD转接头的&#xff1a; 所有下图是我选择…

MyBatis一些常见知识点!

什么是 ORM 框架&#xff1f; MyBatis 有哪些优缺点&#xff1f; 典型回答&#xff1a; ORM&#xff08;Object-Relational Mapping&#xff0c;对象关系映射&#xff09;框架是一种将关系型数据库中的数据 与 应用程序中的对象进行映射的技术。它通过在程序代码中定义的类和属…

c语言--assert断言(详解)

目录 一、断言的概念二、assert断言2.1 代码12.1.1运行结果2.1.2分析 2.2代码22.2.1运行结果2.2.2分析2.3代码32.3.1运行结果及其分析 三、优点四、缺点五、注意 一、断言的概念 assert.h 头⽂件定义了宏 assert() &#xff0c;用于在运行时确保程序符合指定条件&#xff0c;如…

酷开系统 | 拓展内容营销边界,酷开科技大屏价值全面升维

丰富的内容是智能大屏吸引消费者的关键。随着智能大屏各类垂直应用的增多&#xff0c;和长、短视频等多元内容的加入&#xff0c;使消费者的使用需求进一步激发和释放&#xff0c;这些流量的加入&#xff0c;也使大屏成为了营销的天然宝藏。酷开科技一直致力于OTT大屏营销&…

9个Linux 查看系统硬件信息命令(实例详解)

在Linux下&#xff0c;我们精要遇到需要查看系统的硬件信息&#xff0c; 这里我罗列了查看系统硬件信息的实用命令&#xff0c;并做了分类&#xff0c;实例解说。 执行环境&#xff1a;ubuntu 16.04 1. cpu lscpu命令&#xff0c;查看的是cpu的统计信息. rootubuntu:/home/…

使用CHATGPT进行论文写作的缺点和风险

为了真正感受 ChatGPT 的写作潜力&#xff0c;让我们先将其与传统的论文写作方法进行一下比较分析 CHATGPT论文写作的缺点和风险 传统论文写作的考验和磨难很深&#xff1a;费力的研究、组织想法和精心设计的逻辑论证&#xff0c;往往以牺牲你的理智为代价。 进入ChatGPT&am…

【复现】WordPress html5-video-player SQL 注入漏洞_39

目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一&#xff1a; 四.修复建议&#xff1a; 五. 搜索语法&#xff1a; 六.免责声明 一.概述 在WordPress中播放各种视频文件。一个简单&#xff0c;可访问&#xff0c;易于使用和完全可定制的视频播放器&#xff0c;适用于所…

python_蓝桥杯刷题记录_笔记_全AC代码_入门4

题单目录 1.P1914 小书童——凯撒密码 2.P1028 [NOIP2001 普及组] 数的计算 3.P1036 [NOIP2002 普及组] 选数 4.P1149 [NOIP2008 提高组] 火柴棒等式 5.P1217 [USACO1.5] 回文质数 Prime Palindromes 6.P1478 陶陶摘苹果&#xff08;升级版&#xff09; 7.P1618 三连击&…

go消息队列RabbitMQ - 订阅模式-fanout

1、发布订阅 订阅模式&#xff0c;消息被路由投递给多个队列&#xff0c;一个消息被多个消费者获取。 1&#xff09; 可以有多个消费者 2&#xff09; 每个消费者有自己的queue&#xff08;队列&#xff09; 3&#xff09; 每个队列都要绑定到Exchange&#xff08;交换机&…