nginx slice模塊的使用和源碼分析小結(jié)
1. 為什么需要ngx_http_slice_module
顧名思義,nginx的slice模塊的功能是在proxy代理的時候,會將代理到上游服務(wù)器的請求轉(zhuǎn)換為若干個分片的子請求,最后將響應(yīng)內(nèi)容逐個返回給用戶。
那么為什么要搞那么麻煩,將一個大文件切片成小的碎片文件來處理呢?原因有以下三點:
1. 大文件在整個下載的過程中持續(xù)的時間比較長,nginx和上游服務(wù)器(被代理的后端服務(wù)器)長時間建立連接,可能因為各種原因引起連接中斷的概率大幅度上升。
2. 更重要的原因是大文件不利于cdn緩存。譬如著名的開源緩存服務(wù)器ats和squid,一般都需要等文件下載完全后才能將內(nèi)容緩存到cache里面,如果用戶下載了一半不下載了或者因為和上游服務(wù)器連接故障都會導致文件不能完整地被cache服務(wù)器下載下來而導致該文件不能被緩存,引起反復下載,降低內(nèi)容的命中率。
3. 大文件在cdn架構(gòu)中不容易平衡cache節(jié)點的負載,導致cache節(jié)點負載不平衡而影響用戶體驗。
而nginx slice模塊的出現(xiàn)將大文件化整為零,很好地解決了以上這些問題。下面列出了一個CDN cache系統(tǒng)的典型架構(gòu),具體不做詳述,后面可以另行撰文說明。

2. 配置指令
slice size;
其中size是切片的大小,單位可以是K(千字節(jié)),M(兆字節(jié)),G(吉字節(jié)),單位大小寫均可。
slice_size指令可以配置在"http", “server”, “location” 塊中定義。
但是真正要啟用slice功能,還要設(shè)置兩條指令:
proxy_cache_key $uri$is_args$args$slice_range;
proxy_set_header Range $slice_range;
第一條指令表示如果使用nginx的自帶緩存功能,那么nginx會以切片為單位進行緩存,那么緩存的時候需要對同一個文件的不同分片進行區(qū)分,所以需要將cache_key和每個切片的標識進行關(guān)聯(lián),這里使用了$slice_range變量。
第二條指令表示如果向上游服務(wù)器進行請求的時候,需要增加的HTTP Range頭,該頭的內(nèi)容就是$slice_range變量的值。
附帶說明一下:
- $slice_range變量本身是由ngx_http_slice_module來定義并賦值的, 值的內(nèi)容如:`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發(fā)現(xiàn)slice指令,就調(diào)用ngx_conf_set_size_slot函數(shù)進行配置解析。ngx_conf_set_size_slot本身還是非常好理解的, 它是nginx在配置解析階段用來解析大小的通用函數(shù),解析的結(jié)果存放在ngx_http_slice_loc_conf_t的size字段中,如果size字段為0,標識不開啟切片功能。當然需要說明一下,ngx_http_slice_loc_conf_t的實例會由ngx_http_slice_create_loc_conf來創(chuàng)建,并由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是模塊的初始化函數(shù),而ngx_http_slice_add_variables是在preconfiguration階段,也就是在初始化函數(shù)之前執(zhí)行的函數(shù),用來向nginx http框架添加$slice_range變量,供處理http請求的時候使用。
先看一下ngx_http_slice_add_variables函數(shù):
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;
}
以上代碼非常簡單,就是調(diào)用ngx_http_add_variable添加$slice_range變量,變量名存放在全局靜態(tài)變量ngx_http_slice_range_name中,如下:
static ngx_str_t ngx_http_slice_range_name = ngx_string("slice_range");
添加變量的時候還設(shè)置了獲取該變量的回調(diào)函數(shù)為ngx_http_slice_range_variable,$slice_range變量只有g(shù)et沒有set,所以是一個只讀類型的變量,其內(nèi)容只能由slice模塊內(nèi)部進行更新,其他模塊是不能對其進行更新操作的。
再看slice模塊的初始化函數(shù):
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函數(shù)的初始化過程,就是將ngx_http_slice_header_filter和ngx_http_slice_body_filter分別以掛鉤函數(shù)的方式掛入filter模塊的兩條調(diào)用鏈中,即header過濾器調(diào)用鏈和body過濾器調(diào)用鏈。
4.3 slice模塊的上下文
在正式介紹請求處理邏輯之前,需要先了解一下ngx_http_slice_module模塊的請求上下文,具體如下:
typedef struct {
off_t start; /* 當前切片的起始偏移量 */
off_t end; /* 請求內(nèi)容的結(jié)束偏移量,不是指一個切片的結(jié)束偏移量,
而是當前請求客戶端需要的內(nèi)容的結(jié)束偏移量 */
ngx_str_t range; /* 存儲$slice_range變量的字符串值 */
ngx_str_t etag; /* 上游服務(wù)器響應(yīng)的內(nèi)容etag值,
用來比對多個切片請求是否屬于同一個切片 */
unsigned last:1; /* 第一個切片請求是否已經(jīng)完成了最后一個buf的處理 */
unsigned active:1; /* 當前的切片請求響應(yīng)處理過程執(zhí)行中 */
ngx_http_request_t *sr; /* 當前活躍中的子請求 */
} ngx_http_slice_ctx_t;
4.4 $slice_range字段值獲取
為什么從$slice_range字段值的獲取開始說的?因為,這個字段值的獲取在nginx向上游服務(wù)器發(fā)起請求前,組織HTTP請求頭的時候就會被調(diào)用了,執(zhí)行順序上面來說是放在執(zhí)行http header和http body過濾函數(shù)的前面的。而且,獲取這個字段值的時候,會創(chuàng)建slice模塊的請求上下文ngx_http_slice_ctx_t, 另外需要明確的一點是,每發(fā)起一個向上游服務(wù)器的新的切片的請求前,都會重新獲取這個字段值來組織新的請求頭,所以在處理的過程中,這個變量的值是隨著完成的切片的情況而需要不斷更新的。
以下是字段值獲取的回調(diào)函數(shù)的實現(xiàn):
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);
/* 如果為空表示上下文還沒有創(chuàng)建,則需要創(chuàng)建一個新的上下文,
這當然是在主請求上才會有這個情況,子請求不會出現(xiàn)這個情況除非當前filter是disable了,
如果disable狀態(tài),當然返回當前$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;
}
/* 創(chuàng)建一個新的上下文并保存到當前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);
/* 分配一塊內(nèi)存,用于保存$slice_range的值*/
p = ngx_pnalloc(r->pool, sizeof("bytes=-") - 1 + 2 * NGX_OFF_T_LEN);
if (p == NULL) {
return NGX_ERROR;
}
/* 設(shè)置本次需要向上游服務(wù)器發(fā)起的起始位置,詳見下文*/
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;
}
/* 設(shè)置將返回的變量的信息 */
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的函數(shù)調(diào)用是會去判斷客戶端的請求是否是range請求,如果不是range請求,那么很簡單,就是從頭獲取文件的完整內(nèi)容,所以必然是從0字節(jié)開始請求,否則需要解析當前客戶端請求的HTTP Range頭的信息,從而得到客戶端希望的文件起始偏移量。得到的客戶端實際希望的文件起始偏移量以后需要按照切片大小進行對齊后設(shè)置到ctx->start變量中,最后寫入向上游服務(wù)器請求頭中的HTTP Range字段。
那么為什么需要按照slice切片大小進行對齊向上游服務(wù)器請求呢?因為這樣不會由于不同客戶端請求的起始位置不同,導致產(chǎn)生大量的不同切片,引起緩存miss。對齊操作完全就是為了提升緩存的命中率。雖然在本次請求的時候向后端服務(wù)器多請求了一些內(nèi)容,但是比起緩存hit帶來的好處,還是非常非常值得的。
下面是ngx_http_slice_get_start的實現(xiàn)代碼:
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表示向上游服務(wù)器從頭開始請求*/
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.5 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節(jié)中描述的
ngx_http_slice_range_variable中創(chuàng)建的,沒有創(chuàng)建就會返回NULL,
說明本次請求沒有啟用slice過濾模塊,
那么直接調(diào)用ngx_http_next_header_filter執(zhí)行filter鏈中的后續(xù)模塊的處理函數(shù)。
*/
ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module);
if (ctx == NULL) {
return ngx_http_next_header_filter(r);
}
/* 調(diào)用本處理函數(shù)的時候,上游服務(wù)器發(fā)出的響應(yīng)響應(yīng)頭已經(jīng)被本nginx獲取到了。
如果響應(yīng)的內(nèi)容不是206,并且當前是第一個切片的請求
(第一個切片請求只能是主請求發(fā)起,不是子請求),
說明上游服務(wù)器不支持Range請求,則禁用切片功能。
如果是子請求,而上游服務(wù)器已經(jīng)響應(yīng)了非206,那么第一個切片和后續(xù)的切片響應(yīng)
前后不一致,只能報錯了。
*/
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;
}
/* 檢查主請求和自請求中響應(yīng)內(nèi)容的etag是否一致,如果不一致,則認為不是一個內(nèi)容
也只能報錯了。
*/
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;
}
/* 分析上游服務(wù)器的響應(yīng)頭中的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頭中沒有整個內(nèi)容的長度信息,那么不能進行切片處理,只能報錯 */
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);
/* 計算當前切片的結(jié)束偏移位置,也就是下一個切片的起始位置 */
end = ngx_min(cr.start + (off_t) slcf->size, cr.complete_length);
/* 判斷希望請求的切片起止位置和實際上游服務(wù)器響應(yīng)的起止位置是否一致 */
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; /* 設(shè)置下一個切片的開始位置 */
ctx->active = 1; /* 設(shè)置當前的切片請求的響應(yīng)進入活躍狀態(tài)中 */
/* 設(shè)置客戶端響應(yīng)的響應(yīng)頭信息,包括響應(yīng)狀態(tài)需要從206改成200,
內(nèi)容大小改成完整的大小而不是本次切片請求上游服務(wù)器返回的切片大小
*/
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;
/* 向客戶端響應(yīng)的時候需要清理掉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; /*設(shè)置允許ngx_http_range_filter_module執(zhí)行Range處理*/
r->subrequest_ranges = 1;/*本參數(shù)和allow_ranges的值一致的,可以忽略*/
r->single_range = 1; /*設(shè)置ngx_http_range_filter_module僅支持單個Range模式
不支持多Range模式 */
/* 繼續(xù)調(diào)用header filter鏈的下一個模塊的處理函數(shù),后續(xù)模塊可能包括
ngx_http_range_filter_module */
rc = ngx_http_next_header_filter(r);
if (r != r->main) { /* 如果不相等,表示是子請求 */
return rc; /* 如果是子請求就直接返回,不執(zhí)行后面的代碼了 */*
}
/* 以下代碼近在主請求中只會被執(zhí)行1次,子請求中則不會進入到以下代碼 */
/* preserve_body字段的作用就是控制在轉(zhuǎn)發(fā)請求時是否保留請求體。
當preserve_body字段設(shè)置為1時,Nginx將會保留請求體數(shù)據(jù),
并將其傳遞給上游服務(wù)器。當preserve_body字段設(shè)置為0時(默認值),
Nginx會在轉(zhuǎn)發(fā)請求時丟棄請求體數(shù)據(jù),只傳遞請求頭部和其他元數(shù)據(jù)。
*/
r->preserve_body = 1;
/* 如果經(jīng)過header filter的調(diào)用鏈處理后,
ngx_http_range_filter_module處理了客戶端發(fā)送來的Range請求,這個時候
真正發(fā)送給客戶端的狀態(tài)是206響應(yīng),而不是前面設(shè)置的200。因為客戶端的請求是Range請求,
而當前處理的分片的起始范圍在客戶端請求要求的內(nèi)容的起始偏移量前面,
那么需要重新根據(jù)content_offset指定的偏移量調(diào)整向后端服務(wù)器請求的分片起始位置,
而結(jié)束位置為客戶端請求的結(jié)束位置偏移量。
*/
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.6 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過濾模塊,
那么直接調(diào)用ngx_http_next_header_filter執(zhí)行filter鏈中的后續(xù)模塊的處理函數(shù)。
*/
ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module);
if (ctx == NULL || r != r->main) { /* 如果是子請求,直接進入后續(xù)的調(diào)用鏈 */
return ngx_http_next_body_filter(r, in);
}
/* 以下都是在主請求中處理
如果last_buf此字段為1表明這是最后一個buf,但是對于整個請求來說只是一個切片的最后一塊,
不是整個請求的最后一個buf塊,所以需要重新調(diào)整為0,并設(shè)置last_in_chain=1用于表明是本
chain的最后一個buf塊 */
for (cl = in; cl; cl = cl->next) {
if (cl->buf->last_buf) { /* 當前子請求的最后一個buf并不是響應(yīng)給客戶端
的最后一個buf,所以需要重新調(diào)整這個標記 */
cl->buf->last_buf = 0; /* 用于標識是否是最后一個緩沖區(qū) */
cl->buf->last_in_chain = 1; /* 表示是否是鏈表中的最后一個緩沖區(qū) */
cl->buf->sync = 1; /* 表示是否需要執(zhí)行同步操作 */
ctx->last = 1; /* 第一個切片的最后一個buf以及獲取到 */
}
}
/* 調(diào)用body filter鏈后續(xù)filter模塊的處理函數(shù)
第一個切片請求是在主請求中發(fā)生的,這時in里面帶有待發(fā)送到客戶端的數(shù)據(jù)
之后的切片請求是在子請求中發(fā)生的,這個時候子請求的調(diào)用鏈已經(jīng)將數(shù)據(jù)發(fā)送到客戶端了,
到子請求把當前的切片發(fā)送完畢后,會通過發(fā)送一個in=NULL空的包重新激活主請求,
這時主請求可以知道子請求已經(jīng)完成了,從而可以根據(jù)需要開啟一個新的子請求,
或者結(jié)束請求的處理。
*/
rc = ngx_http_next_body_filter(r, in);
if (rc == NGX_ERROR || !ctx->last) {
return rc;
}
/* 當前的子請求還沒有處理完畢,返回nginx http框架,繼續(xù)處理 */
if (ctx->sr && !ctx->sr->done) {
return rc;
}
/* ctx->active=1是在處理子請求頭部信息即ngx_http_slice_header_filter函數(shù)中設(shè)置的
如果=0, 則表示當前切片請求的響應(yīng)還沒有活躍狀態(tài),但是卻需要發(fā)送body,
應(yīng)該是出了什么問題?
*/
if (!ctx->active) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"missing slice response");
return NGX_ERROR;
}
/* 所有內(nèi)容已經(jīng)全部響應(yīng)給客戶端,結(jié)束處理 */
if (ctx->start >= ctx->end) {
/* 因為內(nèi)容已經(jīng)發(fā)送完畢,上下文信息可以清理掉了
ngx_http_set_ctx(r, NULL, ngx_http_slice_filter_module);
/* 這里會通知nginx框架發(fā)送一個last_buf設(shè)置為1的ngx_buf_t緩沖區(qū)表示內(nèi)容發(fā)送完畢 */
ngx_http_send_special(r, NGX_HTTP_LAST);
return rc;
}
/* buffered 字段是一個標志位,用于指示請求是否有未處理的請求體數(shù)據(jù)。
當客戶端發(fā)送一個帶有請求體的 HTTP 請求時,
請求體數(shù)據(jù)可能會被分成多個數(shù)據(jù)塊(chunks)進行傳輸。
buffered 字段用于跟蹤這些請求體數(shù)據(jù)的處理狀態(tài)。
如果這個標記為1, 就暫時不能啟動一個新的子請求 */
if (r->buffered) {
return rc;
}
/* 當前切片已全部響應(yīng)給客戶端,還有新的切片需要處理,開啟一個新的子請求來獲取新的切片 */
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);
/* 設(shè)置下一個切片的$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;
/* 設(shè)置當前切片請求響應(yīng)已經(jīng)結(jié)束 */
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服務(wù),第一個nginx服務(wù)作為前端代理服務(wù)器,第二個nginx服務(wù)作為后端源服務(wù)器,為了簡單起見,將這兩個nginx服務(wù)都搭建在一臺物理服務(wù)器上面。為了能夠一目了然看清楚前端代理服務(wù)器確實向后端發(fā)送了切片請求,需要在后端nginx服務(wù)器的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'
然后設(shè)置前端代理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 / { } 中的設(shè)置。
然后啟動兩個nginx服務(wù),通過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上收到了若干個響應(yīng)為206的HTTP請求,表明前端nginx的切片功能已經(jīng)正常開啟了。
測試用例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,正好對應(yīng)第二個切片的范圍,雖然我們請求要求的起始位置是1048577;同時,最后一個切片請求的結(jié)束位置是6291455,而這個正好是第五個切片的最后一個字節(jié)的偏移量。這樣子驗證了nginx slice功能的切片功能是按照切片對齊的方式向上游服務(wù)器發(fā)送請求的。
到此這篇關(guān)于nginx slice模塊的使用和源碼分析小結(jié)的文章就介紹到這了,更多相關(guān)nginx slice模塊內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Nginx反向代理實現(xiàn)會話(session)保持的兩種方式
這篇文章主要介紹了詳解Nginx反向代理實現(xiàn)會話(session)保持的兩種方式,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-08-08
關(guān)于nginx沒有跳轉(zhuǎn)到upstream地址的解決
這篇文章主要介紹了關(guān)于nginx沒有跳轉(zhuǎn)到upstream地址的解決,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-09-09
Nginx生產(chǎn)環(huán)境平滑升級的實現(xiàn)
本文主要介紹了Nginx生產(chǎn)環(huán)境平滑升級的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-03-03
基于Nginx實現(xiàn)HTTPS網(wǎng)站設(shè)置的步驟
本文主要介紹了Nginx實現(xiàn)HTTPS網(wǎng)站設(shè)置的步驟,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-08-08

