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.rhkb.cn/news/251934.html

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

相关文章

Vue中keep-alive的作用、原理及应用场景

在进行Vue开发的过程中&#xff0c;我们经常会遇到需要进行组件缓存的场景&#xff0c;这时候Vue提供的keep-alive组件就派上了用场。keep-alive组件是Vue内置的一个抽象组件&#xff0c;它可以将其包裹的组件进行缓存&#xff0c;提高组件的性能&#xff0c;同时也可以节省服务…

普渡机器人CEO预测2024年服务机器人市场将扩大

原创 | 文 BFT机器人 根据普渡科技有限公司的报告&#xff0c;商用服务机器人在东亚地区的应用比其他地方更为广泛。然而&#xff0c;预计到2024年&#xff0c;全球其他地区也将迎头赶上。这家总部位于中国深圳的公司自豪地宣称&#xff0c;它已经成为中国最大的此类机器人出口…

前端excel带样式导出 exceljs 插件的使用

本来用的xlsx和xlsx-style两个插件&#xff0c;过程一步一个坑&#xff0c;到完全能用要消灭好多bug。这时发现了exceljs&#xff0c;真香&#x1f600; 案例 <!DOCTYPE html> <html><head><meta charset"utf-8" /><meta name"view…

详解WebRTC rtc::Thread实现

rtc::Thread介绍 rtc::Thread类不仅仅实现了线程这个执行器&#xff08;比如posix底层调用pthread相关接口创建线程&#xff0c;管理线程等&#xff09;&#xff0c;还包括消息队列&#xff08;message_queue)的实现&#xff0c;rtc::Thread启动后就作为一个永不停止的event l…

如何使用MCSM搭建我的世界Java版服务器并实现远程联机游戏

文章目录 1. 安装JAVA2. MCSManager安装3.局域网访问MCSM4.创建我的世界服务器5.局域网联机测试6.安装cpolar内网穿透7. 配置公网访问地址8.远程联机测试9. 配置固定远程联机端口地址9.1 保留一个固定tcp地址9.2 配置固定公网TCP地址9.3 使用固定公网地址远程联机 本教程主要介…

c语言实现greedy snake(贪吃蛇)

##第一个小项目 大一学生寒假项目 最终实现效果如图 一.以C语言实现个人小项目 在我们快速学完了一个高级编程语言&#xff0c;就应该写一个小项目来加以巩固自己的学习成果。 所以今天&#xff0c;我们来尝试写一写greedy snake&#xff0c;对于大学生来说也是可以加强能…

2,cdc放缩位图

类似地&#xff0c;用pDC->StretchBlt来缩放&#xff0c;只是加上了两个参数&#xff0c;原始位图的宽高。 void CMy1_showbitmapView::StretchBitMap(CDC * pDC) { //CBitmap对象 CBitmap bitmap; //CDC对象 CDC dcMemory; //加载资源 bitmap.LoadBitmapW(IDB_BITMAP1); /…

JUnit

前言&#xff1a;自动化就是selenium脚本来实现的&#xff0c;JUnit是java的单元测试工具&#xff0c;只不过我们在实现自动化的时候需要借助一下JUnit库里面提供的一些方法。 目录 1、Test 2、断言—Assertions类 3、用例的执行顺序 3.1 通过order注解来排序 3.2 参数化 …

delete、truncate和drop区别

一、从执行速度上来说 drop > truncate >> DELETE 二、从原理上讲 1、DELETE DELETE from TABLE_NAME where xxx1.1、DELETE属于数据库DML操作语言&#xff0c;只删除数据不删除表的结构&#xff0c;会走事务&#xff0c;执行时会触发trigger&#xff08; 触发器…

ChatGPT之搭建API代理服务

简介 一行Docker命令部署的 OpenAI/GPT API代理&#xff0c;支持SSE流式返回、腾讯云函数 。 项目地址&#xff1a;https://github.com/easychen/openai-api-proxy 这个项目可以自行搭建 OpenAI API 代理服务器工具&#xff0c;该项目是代理的服务器端&#xff0c;不是客户端。…

IP数据云识别真实IP与虚假流量案例

随着互联网的普及&#xff0c;企业在数字领域面临着越来越复杂的网络威胁。为了保护网站免受虚假流量和恶意攻击的影响&#xff0c;许多企业正在采用IP数据云。本文将结合一个真实案例&#xff0c;深入探讨IP数据云如何成功准确地识别真实用户IP和虚假流量IP&#xff0c;提高网…

【数据分享】1929-2023年全球站点的逐日降雪深度数据(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、能见度等指标&#xff0c;说到气象数据&#xff0c;最详细的气象数据是具体到气象监测站点的数据&#xff01; 之前我们分享过1929-2023年全球气象站点的逐日平均气温数据、逐日最高气温数据…

计算机网络_1.6.1 常见的三种计算机网络体系结构

1.6.1 常见的三种计算机网络体系结构 1、OSI&#xff08;七层协议&#xff09;标准失败的原因2、TCP/IP参考模型3、三种网络体系结构对比 笔记来源&#xff1a; B站 《深入浅出计算机网络》课程 1、OSI&#xff08;七层协议&#xff09;标准失败的原因 &#xff08;1&#xf…

[SWPUCTF 2021 新生赛]easyupload1.0

发现是上传文件第一想到是文件木马 <?php eval ($_POST[123]);?>木马上传burp修改后缀发现flag里面这个是假的 我们猜想是在phpinfo我们上传<?php eval(phpinfo(););?>木马上传burp修改后缀里面 CtrlF 发现flag

IT行业证书的获取与价值:提升职业竞争力的关键

目录 IT行业证书的价值和作用 1. Cisco&#xff08;思科&#xff09;认证&#xff08;如CCNA、CCNP、CCIE&#xff09;&#xff1a; 2. 微软认证&#xff08;如MCSA、MCSE、MCSD&#xff09;&#xff1a; 3. 计算机网络技术&#xff08;CompTIA Network、CompTIA Security&a…

亲测解决vscode的debug用不了、点了没反应

这个问题在小虎登录vscode同步了设置后出现,原因是launch文件被修改或删除。解决方法是重新添加launch。 坏境配置 win11 + vscode 解决方法 Ctrl + shift + P,搜索debug添加配置: 选择python debugger。 结果生成了一个文件在当前路径: launch内容: {// Use Int…

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

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

雷达DoA估计的跨行业应用--麦克风阵列声源定位(Matlab仿真)

一、概述 麦克风阵列&#xff1a; 麦克风阵列是由一定数目的声学传感器&#xff08;麦克风&#xff09;按照一定规则排列的多麦克风系统&#xff0c;而基于麦克风阵列的声源定位是指用麦克风拾取声音信号&#xff0c;通过对麦克风阵列的各路输出信号进行分析和处理&#xff0c;…

【计算机网络】物理层概述|通信基础|奈氏准则|香农定理|信道复用技术

目录 一、思维导图 二、 物理层概述 1.物理层概述 2.四大特性&#xff08;巧记"械气功程") 三、通信基础 1.数据通信基础 2.趁热打铁☞习题训练 3.信号の变身&#xff1a;编码与调制 4.极限数据传输率 5.趁热打铁☞习题训练 6.信道复用技术 推荐 前些天发…

虹科技术|一文详解IO-Link Wireless技术如何影响工业无线自动化

导读&#xff1a;在工业无线自动化的飞速发展进程中&#xff0c;IO-Link Wireless技术成为了一项具有颠覆性的创新。它将IO-Link协议与无线连接完美结合&#xff0c;解决了传统通信技术在工业应用中的痛点。本文将深入解析IO-Link Wireless技术的原理、应用领域、优势以及实际案…