Nginx的slice模塊能夠將一個請求分解成多個子請求,每一個子請求返回響應內容的一個片斷,讓大文件的緩存更有效率。html
HTTP客戶端下載文件時,若是發生了網絡中斷,必須從新向服務器發起HTTP請求,這時客戶端已經有了文件的一部分,只須要請求剩餘的內容,而不須要傳輸整個文件,Range請求就能夠用來處理這種問題。nginx
若是HTTP請求的頭部有Range字段,以下面所示緩存
Range: bytes=1024-2047
表示客戶端請求文件的第1025到第2048個字節,這時服務器只會響應文件的這部份內容,響應的狀態碼爲206,表示返回的是響應的一部分。若是服務器不支持Range請求,仍然會返回整個文件,這時狀態碼還是200。服務器
ngx_http_slice_filter_module模塊默認沒有編譯到Nginx程序中,須要編譯時添加--with-http_slice_module選項。網絡
編譯完成後, 須要在Nginx配置文件中開啓,配置以下所示less
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指令設置分片的大小爲1m。 這裏使用了proxy_set_header指令,在取源時的HTTP請求中添加了Range頭部,向源服務器請求文件的一部分,而不是所有內容。在proxy_cache_key中添加slice_range變量這樣能夠分片緩存。函數
slice_range這個變量做用很是特殊,這個變量的值是當前須要向源服務器請求的分片,若是分片的大小爲1m,那麼最開始變量的值爲bytes=0-1048575
,經過配置文件中的proxy_set_header Range $slice_range;
能夠知道取源時請求的Range頭部爲Range:bytes=0-1048575
,源服務器若是支持Range請求,便會返回響應的前1m字節,獲得這個響應後slice_range變量的值變爲bytes=1048576-2097171
,再次取源時便會取後1m字節,依次直到取得所有響應內容。debug
Nginx的slice模塊是經過掛載filter模塊來起做用的,處理流程以下所示code
slice模塊的body_filter處理在ngx_http_slice_body_filter函數中server
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; ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module); /* 若是當前請求是子請求,直接調用下一個body_filter回調而後返回,*/ if (ctx == NULL || r != r->main) { return ngx_http_next_body_filter(r, in); } 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; } /* 若是當前的子請求尚未接受徹底部的響應會直接返回,這裏子請求向源請求,獲得響應後由這裏的主請求發送給客戶端,子請求只負責取源。當前子請求接收徹底部的響應(這時 ctx->sr->done爲1)後,主請求才會生成下一個子請求去取下一個分片 */ if (ctx->sr && !ctx->sr->done) { return rc; } 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); ngx_http_send_special(r, NGX_HTTP_LAST); return rc; } if (r->buffered) { return rc; } /* 生成子請求,注意這裏的NGX_HTTP_SUBREQUEST_CLONE,默認生成子請求從server_rewrite階段執行並跳過access階段,這裏NGX_HTTP_SUBREQUEST_CLONE使生成的子請求從主請求的當前階段(即content階段)開始執行 */ 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); /* ctx->range指向的使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; }
請求中的Range範圍可能會超過文件的大小,如第一次取源時,Nginx並不知道實際文件的大小,因此Nginx請求時老是按照分片的大小設置Range範圍,如slice設置爲1m,那麼第一次取bytes=0-1048575
,若是文件不足1m,響應狀態嗎爲200,表示不須要分片。若是超過1m,第二次取源時Range字段爲bytes=1048576-2097171
,即便這時能夠知道文件實際大小。
線上使用時就遇到過一次源服務器對Range請求支持不完善的問題,文件大小爲1.5m,第一次取源狀態碼爲206,返回1m內容,第二次取源使Range字段爲bytes=1048576-2097171
,可是文件不足2m,源服務器發現這個範圍超過了文件大小,因此返回了整個文件,狀態碼爲200,這時Nginx就不能理解了,直接報錯中斷了響應。
開始覺得是Nginx的問題,而後查看了下RFC文檔,發現有解釋這種狀況
A client can limit the number of bytes requested without knowing the size of the selected representation. If the last-byte-pos value is absent, or if the value is greater than or equal to the current length of the representation data, the byte range is interpreted as the remainder of the representation (i.e., the server replaces the value of last-byte-pos with a value that is one less than the current length of the selected representation).
大體意思是說,若是請求的分片的後一個偏移超過了文件的實際大小,服務器應該返回剩餘的部份內容。這個問題應該是源服務器的實現並無按照RFC文檔的要求。