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

news2024/11/25 7:16:00

文章目录

  • 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.coloradmin.cn/o/1433844.html

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

相关文章

云计算市场分析

目录 一、云计算市场概述 1.1 概述 二、国外云计算厂商 2.1 亚马逊AWS 2.2 微软AzureAzure 2.3 Apple iCloud 三、国内云计算厂商 3.1 阿里云 3.2 腾讯云 3.3 华为云 3.4 百度智能云 一、云计算市场概述 1.1 概述 云计算从出现以来&#xff0c;其发展就非常迅速。以…

【SeaArt】免费生成图像——功能和使用方法解析

SeaArt 关于SeaArtSeaArt的特点和功能1. SeaArt每天最多可以免费生成150次2. SeaArt生成的图片可以用于商业用途 如何使用登录创作 总结 关于SeaArt SeaArt&#xff08;海艺&#xff09;是由总部位于新加坡的“STAR CLUSTER PTE. LTD.”运营的图像生成AI工具。 它基于Stable …

7.Vue面试题

一、Vue面试基础知识 在这一小节中&#xff0c;我们先把一些常见的Vue的基础的面试题&#xff0c;总结出来。这些基础的知识点都是在面试的时候经常会被问到的一些内容。 当然关于基础的一些内容在前面的课程总咱们都已经讲解过来&#xff0c;所以这里我们只是把一些常见的内…

C++PythonC# 三语言OpenCV从零开发(8):图像平滑处理

文章目录 相关链接前言图像资源图像平滑处理图像学知识补充(重点)什么是卷积什么是图像滤波什么是方框滤波和均值滤波 代码PythonCCsharp 总结 相关链接 C&Python&Csharp in OpenCV 专栏 【2022B站最好的OpenCV课程推荐】OpenCV从入门到实战 全套课程&#xff08;附带课…

UE4运用C++和框架开发坦克大战教程笔记(十七)(第51~54集)

UE4运用C和框架开发坦克大战教程笔记&#xff08;十七&#xff09;&#xff08;第51~54集&#xff09; 51. UI 框架介绍UE4 使用 UI 所面临的问题以及解决思路关于即将编写的 UI 框架的思维导图 52. 管理类与面板类53. 预加载与直接加载54. UI 首次进入界面 51. UI 框架介绍 U…

c#string方法对比

字符串的截取匹配操作在开发中非常常见&#xff0c;比如下面这个示例&#xff1a;我要匹配查找出来字符串数组中以“abc”开头的字符串并打印&#xff0c;我下面分别用了两种方式实现&#xff0c;代码如下&#xff1a; using System; namespace ConsoleApp23{ class Progra…

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

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

flutter开发实战-可扩展popup弹窗template模版样式

flutter开发实战-可扩展popup弹窗template模版样式 最近在看到一个flutter_beautiful_popup&#xff0c;可以美化弹窗窗口样式。该插件通过一个template模版的类BeautifulPopupTemplate作为抽象的base类。 一、基类BeautifulPopupTemplate 在BeautifulPopupTemplate中&…

前端面试题——Vue的双向绑定

前言 双向绑定机制是Vue中最重要的机制之一&#xff0c;甚至可以说是Vue框架的根基&#xff0c;它将数据与视图模板相分离&#xff0c;使得数据处理和页面渲染更为高效&#xff0c;同时它也是前端面试题中的常客&#xff0c;接下来让我们来了解什么是双向绑定以及其实现原理。…

可解释性对人工智能发展的影响

文章目录 每日一句正能量前言可解释AI已成热点可解释性人工智能的重要性可解释性人工智能的研究现状推动可解释模型构建未来展望后记 每日一句正能量 不好等待运气降临&#xff0c;就应去发奋掌握知识。 前言 随着人工智能技术的快速发展&#xff0c;越来越多的应用场景需要人…

神经网络激活函数到底是什么?

激活函数 其实不是很难啦&#xff0c;归结一下就是大概这样几个分类&#xff0c;详情请参考【神经网络】大白话直观理解&#xff01;_哔哩哔哩_bilibili神经网络就是干这个事的~ 如果队伍不长&#xff0c;一个ykxb就可以了&#xff0c;如果 如果 队伍太长 就加一个激活函数也…

HBase相关面试准备问题

为什么选择HBase 1、海量存储 Hbase适合存储PB级别的海量数据&#xff0c;在PB级别的数&#xff0c;能在几十到几百毫秒内返回数据。这与Hbase的极易扩展性息息相关。正是因为Hbase良好的扩展性&#xff0c;才为海量数据的存储提供了便利。 2、列式存储 这里的列式存储其实说的…

Verilog实现2进制码与BCD码的互相转换

1、什么是BCD码&#xff1f; BCD码是一种2进制的数字编码形式&#xff0c;用4位2进制数来表示1位10进制中的0~9这10个数。这种编码技术&#xff0c;最常用于会计系统的设计里&#xff0c;因为会计制度经常需要对很长的数字做准确的计算。相对于一般的浮点式记数法&#xff0c;…

微信小程序 --- 腾讯地图线路规划

目录 微信小程序JavaScript 简介 Hello world&#xff01; geocoder(options:Object) 微信小程序插件 简介 路线规划插件 入驻腾讯位置服务平台 申请开发者密钥&#xff08;Key&#xff09;&#xff1a;申请秘钥 Key的作用与注意事项 微信公众平台绑定插件 方式一&a…

新型IT运维管理,基础设施和数据两手都要硬

编前语&#xff1a;数据是AI的基石&#xff0c;缺数据无AI。 AI大模型时代&#xff0c;数据赋予IT人“新使命” 当下IT人在企业中扮演着运营支撑的角色。说到运维管理&#xff0c;相信每人都是一把辛酸泪&#xff0c;每天承担着繁琐、高负荷且又高风险的运维工作&#xff0c;但…

开源软件全景解析:驱动技术创新与行业革新的力量

目录 什么是开源 开源的核心 开源软件的特点 为什么程序员应该拥抱开源 1.学习机会&#xff1a; 2.社区支持&#xff1a; 3.提高职业竞争力&#xff1a; 4.加速开发过程&#xff1a; 5.贡献和回馈&#xff1a; 开源软件的影响力 开源软件多元分析&#xff1a; 开源…

机器学习中常用的性能度量—— ROC 和 AUC

什么是泛化能力&#xff1f; 通常我们用泛化能力来评判一个模型的好坏&#xff0c;通俗的说&#xff0c;泛化能力是指一个机器学期算法对新样本&#xff08;即模型没有见过的样本&#xff09;的举一反三的能力&#xff0c;也就是学以致用的能力。 举个例子&#xff0c;高三的…

为什么说TiDB在线扩容对业务几乎没有影响

作者&#xff1a; 数据源的TiDB学习之路 原文来源&#xff1a; https://tidb.net/blog/e82b2c5f 当前的数据库种类繁多&#xff0c;墨天轮当前统计的所有国产数据库已经有 290个 &#xff0c;其中属于关系型数据库的有 166个 。关系型数据库从部署架构上又可以分为集中式…

AI克隆自己的声音只需5秒,MockingBird实现AI克隆声音!

一、环境搭建 搭建Anaconda运行环境 搭建Anaconda运行环境请跳转链接查看https://blog.csdn.net/m0_50269929/article/details/136036402安装pytorch pip3 install torch torchvision torchaudio安装ffmpeg 打开官网 https://ffmpeg.org/download.html#get-packages 下载地址…

Python实现PDF到HTML的转换

PDF文件是共享和分发文档的常用选择&#xff0c;但提取和再利用PDF文件中的内容可能会非常麻烦。而利用Python将PDF文件转换为HTML是解决此问题的理想方案之一&#xff0c;这样做可以增强文档可访问性&#xff0c;使文档可搜索&#xff0c;同时增强文档在不同场景中的实用性。此…