ngx_http_range_filter_module.c模組原始碼分析
ngx_http_range_filter_module.c模組原始碼分析
原文文章 http://blog.csdn.net/apelife/article/details/73611716
http range官方文件 https://tools.ietf.org/html/rfc7233
什麼是Range?
當使用者在聽一首歌的時候,如果聽到一半(網路下載了一半),網路斷掉了,使用者需要繼續聽的時候,檔案伺服器不支援斷點的話,則使用者需要重新下載這個檔案。而Range支援的話,客戶端應該記錄了之前已經讀取的檔案範圍,網路恢復之後,則向伺服器傳送讀取剩餘Range的請求,服務端只需要傳送客戶端請求的那部分內容,而不用整個檔案傳送回客戶端,以此節省網路頻寬。
Range的格式
➜ /tmp curl -H "Range: bytes=0-10"http://127.0.0.1:8180/bg-upper.png -v
* Hostname was NOT found in DNS cache
* Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 8180 (#0)
> GET /bg-upper.png HTTP/1.1
> User-Agent: curl/7.35.0
> Host: 127.0.0.1:8180
> Accept: */*
> Range: bytes=0-10
>
< HTTP/1.1 206 Partial Content
* Server Apache-Coyote/1.1 is not blacklisted
< Server: Apache-Coyote/1.1
< Accept-Ranges: bytes
< ETag: W/"3103-1435633968000"
< Last-Modified: Tue, 30 Jun 2015 03:12:48 GMT
< Content-Range: bytes 0-10/3103
< Content-Type: image/png
< Content-Length: 11
< Date: Tue, 29 Dec 2015 09:18:36 GMT
客戶端Range: bytes=0-10表示請求第0到10個位元組資料。 服務端中Content-Range: bytes 0-10/3103表示回應0-10位元組資料,總共有3103個位元組資料。Accept-Ranges表示服務端支援range傳輸,支援格式為byte
ngx_http_range_filter_module.c模組原始碼分析
nginx斷點續傳功能是由ngx_http_range_filter_module實現的。其實這個模組是由兩個模組組成的,一個為ngx_http_range_header_filter_module, 用於設定http響應的頭部資訊,例如: 設定content-range,指定應答的區間塊開始結束位置; 設定content-length, 指定斷點續傳時的應答包體大小; 設定206響應碼而不是200響應碼等等。 另一個模組為ngx_http_range_body_filter_module, 用於從緩衝區中查詢指定區間塊內容,並把這個區間塊的內容發給客戶端。
一、模組初始化
在Nginx中,每個Http模組在初始化時,會找到連結串列的首元素ngx_http_top_header_filter和ngx_http_top_body_filter指標,再在自己檔案中使用自定義的靜態指標將自己插入連結串列首部
static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
//將http_range_heder過濾模組插入過濾頭連結串列
static ngx_int_t
ngx_http_range_header_filter_init(ngx_conf_t *cf)
{
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_range_header_filter;
return NGX_OK;
}
//將http_range_body_heder過濾模組插入過濾包體連結串列
static ngx_int_t ngx_http_range_body_filter_init(ngx_conf_t *cf)
{
ngx_http_next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_range_body_filter;
return NGX_OK;
}
二、ngx_http_range_header_filter_module模組分析
ngx_http_range_header_filter函式功能: 不能支援range格式時跳過當前過濾模組,進入下一個過濾模組處理; 當用來給客戶端的請求頭有帶range資訊但請求range失敗時,給返回的頭部中插入”Accept-Ranges:bytes”頭。 解析客戶端傳送過來的Range頭,其首先檢查一下客戶端頭部是否帶Range,如果客戶端頭部有 If-Range ,先檢測是否etag存在,若不存在則檢查last_modified_time的時間,是否和服務端相等,如果不相等,表示伺服器的檔案比客戶端已經下載的不一樣了,需要重新下載全部檔案 呼叫ngx_http_range_parse函式解析客戶端的range頭部,根據返回結果進行相應處理
static ngx_int_t
ngx_http_range_header_filter(ngx_http_request_t *r)
{
//nginx過濾頭部連結串列的入口
time_t if_range_time;
ngx_str_t *if_range, *etag;
ngx_uint_t ranges;
ngx_http_core_loc_conf_t *clcf;
ngx_http_range_filter_ctx_t *ctx;
if (r->http_version < NGX_HTTP_VERSION_10
|| r->headers_out.status != NGX_HTTP_OK
|| (r != r->main && !r->subrequest_ranges)
|| r->headers_out.content_length_n == -1
|| !r->allow_ranges)
{//1.0 以下版本|非200回應的請求非|是子請求且不支援range|傳送失敗|不允許range跳過
return ngx_http_next_header_filter(r);
}
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
if (clcf->max_ranges == 0) {
return ngx_http_next_header_filter(r);
}
if (r->headers_in.range == NULL
|| r->headers_in.range->value.len < 7
|| ngx_strncasecmp(r->headers_in.range->value.data , (u_char *) "bytes=", 6) != 0)
{//如果客戶端傳送過來的頭部資料中沒有range欄位,則我們也不需要處理range。直接返回即可
goto next_filter;
}
if (r->headers_in.if_range) {
//語法為If-Range = "If-Range" ":" ( entity-tag | HTTP-date )
if_range = &r->headers_in.if_range->value;
//判斷是否存在etag
if (if_range->len >= 2 && if_range->data[if_range->len - 1] == '"') {
if (r->headers_out.etag == NULL) {
goto next_filter;
}
etag = &r->headers_out.etag->value;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,"http ir:%V etag:%V", if_range, etag);
if (if_range->len != etag->len
|| ngx_strncmp(if_range->data, etag->data, etag->len) != 0)
{//*與輸出的etag比較,一致則返回Range內容,否則返回所有內容*
goto next_filter;
}
goto parse;
}
if (r->headers_out.last_modified_time == (time_t) -1) {
goto next_filter;
}
if_range_time = ngx_parse_http_time(if_range->data, if_range->len);
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http ir:%T lm:%T",
if_range_time, r->headers_out.last_modified_time);
if (if_range_time != r->headers_out.last_modified_time) {
goto next_filter;//時間跟伺服器上的時間不相等,需要返回所有的。
}
}
parse:
ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_range_filter_ctx_t));
if (ctx == NULL) {
return NGX_ERROR;
}
ctx->offset = r->headers_out.content_offset;
if (ngx_array_init(&ctx->ranges, r->pool, 1, sizeof(ngx_http_range_t))
!= NGX_OK)
{
return NGX_ERROR;
}
ranges = r->single_range ? 1 : clcf->max_ranges;
/*解析頭部送的Range欄位 例如 Range: bytes=0-1024 */
switch (ngx_http_range_parse(r, ctx, ranges)) {
case NGX_OK://設定上下文到body過濾模組
ngx_http_set_ctx(r, ctx, ngx_http_range_body_filter_module);
//修改傳送的狀態碼200為206
r->headers_out.status = NGX_HTTP_PARTIAL_CONTENT;
r->headers_out.status_line.len = 0;
if (ctx->ranges.nelts == 1) {
//單個RANGE
return ngx_http_range_singlepart_header(r, ctx);
}
//多個range,比如"Range: bytes=500-600,601-999"
return ngx_http_range_multipart_header(r, ctx);
case NGX_HTTP_RANGE_NOT_SATISFIABLE:
/客戶端請求的Range欄位格式不正確, 直接返回給客戶端Content-Range:byte :*/size
return ngx_http_range_not_satisfiable(r);
case NGX_ERROR:
return NGX_ERROR;
default: /* NGX_DECLINED */
break;
}
next_filter:
r->headers_out.accept_ranges = ngx_list_push(&r->headers_out.headers);
if (r->headers_out.accept_ranges == NULL) {
return NGX_ERROR;
}
r->headers_out.accept_ranges->hash = 1;
//輸出 Accept-Ranges : bytes 欄位告訴客戶端, 伺服器支援分段下載
ngx_str_set(&r->headers_out.accept_ranges->key, "Accept-Ranges");
ngx_str_set(&r->headers_out.accept_ranges->value, "bytes");
return ngx_http_next_header_filter(r);
}
ngx_http_range_parse函式用於解析http請求頭部的range欄位,將多個區間儲存到ctx->ranges陣列中, 例如:range:byte=0-499,500-1000,1000- 則將這三個區間儲存到陣列中。函式的實現很簡單,就是解析range頭部, 找到每一個區間塊的開始位置、結束位置。 對於當個range只在新增頭部資訊Content-Range: bytes START-END/SIZE" CRLF,資料就放在body裡面即可,不需要用multipart的方式組織資料。單個range請求的返回資料形如下
"HTTP/1.0 206 Partial Content" CRLF
… header …
“Content-Type: image/jpeg” CRLF
“Content-Length: SIZE” CRLF
“Content-Range: bytes START-END/SIZE” CRLF
CRLF
… data …
ngx_http_range_singlepart_header原始碼
static ngx_int_t
ngx_http_range_singlepart_header(ngx_http_request_t *r,
ngx_http_range_filter_ctx_t *ctx)
{//客戶端只請求了一個range,那麼我們返回的資料格式為: Content-Range: bytes START-END/SIZE" CRLF
ngx_table_elt_t *content_range;
ngx_http_range_t *range;
//非主請求跳過
if (r != r->main) {
return ngx_http_next_header_filter(r);
}
//在輸出頭裡面申請一個header line
content_range = ngx_list_push(&r->headers_out.headers);
if (content_range == NULL) {
return NGX_ERROR;
}
r->headers_out.content_range = content_range;
content_range->hash = 1;
ngx_str_set(&content_range->key, "Content-Range");
//申請三個足夠長度的數字的字串
content_range->value.data = ngx_pnalloc(r->pool,
sizeof("bytes -/") - 1 + 3 * NGX_OFF_T_LEN);
if (content_range->value.data == NULL) {
return NGX_ERROR;
}
/* "Content-Range: bytes SSSS-EEEE/TTTT" header */
range = ctx->ranges.elts;
//設定START-END/SIZE格式。
content_range->value.len = ngx_sprintf(content_range->value.data,"bytes %O-%O/%O",range->start, range->end - 1,r->headers_out.content_length_n)- content_range->value.data;
r->headers_out.content_length_n = range->end - range->start;
r->headers_out.content_offset = range->start;
//清空header中content_length,因為已有content_length_n直接表示長度
if (r->headers_out.content_length) {
r->headers_out.content_length->hash = 0;
r->headers_out.content_length = NULL;
}
return ngx_http_next_header_filter(r);
但是對於一次請求多個區塊的請求,其處理相對複雜。因為返回的body資料無法直接放在BODY裡面傳送,需要組織成multipart 的形式才行了。多個range請求時返回資料的格式如下
“HTTP/1.0 206 Partial Content” CRLF
… header …
“ Content-Type: multipart/byteranges; boundary=0123456789″ CRLF
CRLF//頭部結束
CRLF
“–0123456789″ CRLF
“Content-Type: image/jpeg” CRLF
“Content-Range: bytes START0-END0/SIZE” CRLF
CRLF
… data …
CRLF
“–0123456789″ CRLF
“Content-Type: image/jpeg” CRLF
“Content-Range: bytes START1-END1/SIZE” CRLF
CRLF
… data …
CRLF
"–0123456789–" CRLF
ngx_http_range_multipart_header函式首先呼叫ngx_next_temp_number獲取一個臨時數字當做boundary,然後組成boundary_header
static ngx_int_t
ngx_http_range_multipart_header(ngx_http_request_t *r,
ngx_http_range_filter_ctx_t *ctx)
{
size_t len;
ngx_uint_t i;
ngx_http_range_t *range;
ngx_atomic_uint_t boundary;
len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN
+ sizeof(CRLF "Content-Type: ") - 1
+ r->headers_out.content_type.len
+ sizeof(CRLF "Content-Range: bytes ") - 1;
/*判斷是否需要新增charset欄位, 如果新增charset欄位, content_type.len 和 content_type_len值將不會相等.*/
if (r->headers_out.content_type_len == r->headers_out.content_type.len
&& r->headers_out.charset.len)
{
len += sizeof("; charset=") - 1 + r->headers_out.charset.len;
}
ctx->boundary_header.data = ngx_pnalloc(r->pool, len);
if (ctx->boundary_header.data == NULL) {
return NGX_ERROR;
}
//呼叫ngx_next_temp_number獲取一個臨時數字當做boundary,然後組成boundary_header
boundary = ngx_next_temp_number(0);
/*
* The boundary header of the range:
* CRLF
* "--0123456789" CRLF
* "Content-Type: image/jpeg" CRLF
* "Content-Range: bytes "
*/
//拼接bondery頭,也就是--xxxxx以及後面的那幾行頭部資料。頭部資料之後就是bonder的資料部分了。
if (r->headers_out.content_type_len == r->headers_out.content_type.len
&& r->headers_out.charset.len)
{
ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data,
CRLF "--%0muA" CRLF
"Content-Type: %V; charset=%V" CRLF
"Content-Range: bytes ",
boundary,
&r->headers_out.content_type,
&r->headers_out.charset)
- ctx->boundary_header.data;
} else if (r->headers_out.content_type.len) {
ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data,
CRLF "--%0muA" CRLF
"Content-Type: %V" CRLF
"Content-Range: bytes ",
boundary,
&r->headers_out.content_type)
- ctx->boundary_header.data;
} else {
ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data,
CRLF "--%0muA" CRLF
"Content-Range: bytes ",
boundary)
- ctx->boundary_header.data;
}
r->headers_out.content_type.data =
ngx_pnalloc(r->pool,
sizeof("Content-Type: multipart/byteranges; boundary=") - 1
+ NGX_ATOMIC_T_LEN);
if (r->headers_out.content_type.data == NULL) {
return NGX_ERROR;
}
r->headers_out.content_type_lowcase = NULL;
/* "Content-Type: multipart/byteranges; boundary=0123456789" */
/* 設定頭部Content-Type型別,標識為多段內容, 並指明分隔符boundary欄位 */
r->headers_out.content_type.len =
ngx_sprintf(r->headers_out.content_type.data,
"multipart/byteranges; boundary=%0muA",
boundary)
- r->headers_out.content_type.data;
r->headers_out.content_type_len = r->headers_out.content_type.len;
r->headers_out.charset.len = 0;
/* the size of the last boundary CRLF "--0123456789--" CRLF */
len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN + sizeof("--" CRLF) - 1;
range = ctx->ranges.elts;
//一個個將range的開始,結束部分字串進行拼接了,也就是”SSSS-EEEE/TTTT” CRLF CRLF”
for (i = 0; i < ctx->ranges.nelts; i++) {
/* the size of the range: "SSSS-EEEE/TTTT" CRLF CRLF */
range[i].content_range.data =
ngx_pnalloc(r->pool, 3 * NGX_OFF_T_LEN + 2 + 4);
if (range[i].content_range.data == NULL) {
return NGX_ERROR;
}
range[i].content_range.len = ngx_sprintf(range[i].content_range.data,
"%O-%O/%O" CRLF CRLF,
range[i].start, range[i].end - 1,
r->headers_out.content_length_n)
- range[i].content_range.data;
len += ctx->boundary_header.len + range[i].content_range.len
+ (size_t) (range[i].end - range[i].start);
}
r->headers_out.content_length_n = len;
if (r->headers_out.content_length) {
r->headers_out.content_length->hash = 0;
r->headers_out.content_length = NULL;
}
return ngx_http_next_header_filter(r);
}
ngx_http_range_not_satisfiable 在頭部新增Content-Range:byte:*/ 到這裡頭部資料的過濾函式完畢了,總結一下就是
1.呼叫ngx_http_range_parse解析客戶端請求頭:“Range: bytes=0-1024”。
2.如果是單個Range,呼叫ngx_http_range_singlepart_header拼接”Content-Range: bytes START-END/SIZE”頭。
3.如果是多個Range請求,呼叫ngx_http_range_multipart_header準備multipart資料,以便後面body filter使用
三、包體過濾函式
ngx_http_range_body_filter判斷一下ctx->ranges.nelts的數量,如果是單個Range請求,就呼叫ngx_http_range_singlepart_body完成工作,否則判斷一下是否所有的ranges都在一塊buf裡面,nginx目前只支援所有的ranges都在一塊buf裡面的情況,並且也只支援所有的都在buf連結串列的的一塊buf裡面。
static ngx_int_t
ngx_http_range_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
//http過濾body模組連結串列呼叫這裡
ngx_http_range_filter_ctx_t *ctx;
if (in == NULL) {
return ngx_http_next_body_filter(r, in);
}
ctx = ngx_http_get_module_ctx(r, ngx_http_range_body_filter_module);
if (ctx == NULL) {
return ngx_http_next_body_filter(r, in);
}
//如果只有一個range
if (ctx->ranges.nelts == 1) {
return ngx_http_range_singlepart_body(r, ctx, in);
}
/*
* multipart ranges are supported only if whole body is in a single buffer
*/
//特殊buff,讓下一個模組處理
if (ngx_buf_special(in->buf)) {
return ngx_http_next_body_filter(r, in);
}
//檢查是否所有的RANGE都在一塊buf裡面
if (ngx_http_range_test_overlapped(r, ctx, in) != NGX_OK) {
return NGX_ERROR;
}
//多個range
return ngx_http_range_multipart_body(r, ctx, in);
}
(1)請求單個區間塊的資料 考慮下,如果給客戶端響應的應答連結串列由5個資料結點組成,每一個資料結點都存放了要發給客戶端的資料, 則剛開始的記憶體佈局如下;
當客戶端指定了range, 只請求這個應答連結串列中的部分資料時,則可能的記憶體佈局如下圖: 從圖中可以看出第一個資料結點不需要發給客戶端,該緩衝區是沒有用了,因此將pos與last都指向緩衝區的末尾。最後一個資料結點也是不需要傳送給客戶端, 但並沒有將pos與last指向緩衝區的末尾,因為這個結點會從連結串列中脫離(可以看下一張圖),因此可以不需要把pos與last都指向緩衝區的末尾。
只有中間的三個結點中的資料需要傳送給客戶端,記憶體佈局如下: 可以看出這三個區間塊中的最後一個結點的next指標為null 對於沒有傳送給客戶端的資料節點,這些節點沒有用了,待請求關閉時,這些資料節點會被回收。看下函式ngx_http_range_singlepart_body是如何從緩衝區中查詢單個資料區間,並把這個資料區間的內容傳送給客戶端的。
static ngx_int_t
ngx_http_range_singlepart_body(ngx_http_request_t *r,
ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in)
{
off_t start, last;
ngx_buf_t *buf;
ngx_chain_t *out, *cl, **ll;
ngx_http_range_t *range;
out = NULL;
ll = &out;
range = ctx->ranges.elts;
for (cl = in; cl; cl = cl->next) {
buf = cl->buf;
start = ctx->offset;//在所有資料中的位置。
last = ctx->offset + ngx_buf_size(buf);
ctx->offset = last;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http range body buf: %O-%O", start, last);
if (ngx_buf_special(buf)) {
*ll = cl;
ll = &cl->next;
continue;
}
if (range->end <= start || range->start >= last) {//丟棄這個。將其pos指向last從而略過。
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http range body skip");
if (buf->in_file) {
buf->file_pos = buf->file_last;
}
buf->pos = buf->last;
buf->sync = 1;
continue;
}
if (range->start > start)
{//當前塊滿足區間,則檢視開始資料位置
if (buf->in_file) {
buf->file_pos += range->start - start;
}
if (ngx_buf_in_memory(buf)) {
buf->pos += (size_t) (range->start - start);
}
}
if (range->end <= last) {
if (buf->in_file) {
buf->file_last -= last - range->end;
}
if (ngx_buf_in_memory(buf)) {
buf->last -= (size_t) (last - range->end);
}
buf->last_buf = 1;//標記為最後一塊記憶體,後面的都不需要了。
*ll = cl;
cl->next = NULL;
break;
}
*ll = cl;
ll = &cl->next;
}
//到這裡後,out變數所指向的連結串列裡面的資料都是range之間的
if (out == NULL) {
return NGX_OK;
}
return ngx_http_next_body_filter(r, out);
}
(2)請求多個區間塊的資料 nginx伺服器目前的實現方式: 請求多個區間塊時,應答連結串列只能由一個結點組成,而不像請求單個區間,應答連結串列可以由多個節點組成。假設有一個檔案的內容為: "0123456789abcdef",一共16個位元組。如果客戶端想要請求兩個區間塊的資料, 則可以在http請求頭部加上range欄位, 格式為: range: bytes=0-5, 9-13; 這樣nginx伺服器就只會把012345以及9abcd返回給客戶端,而不會傳送整個檔案的資料。參考下面的這張圖:
static ngx_int_t
ngx_http_range_multipart_body(ngx_http_request_t *r,
ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in)
{
ngx_buf_t *b, *buf;
ngx_uint_t i;
ngx_chain_t *out, *hcl, *rcl, *dcl, **ll;
ngx_http_range_t *range;
ll = &out;
buf = in->buf;
range = ctx->ranges.elts;
for (i = 0; i < ctx->ranges.nelts; i++) {
/*
* The boundary header of the range:
* CRLF
* "--0123456789" CRLF
* "Content-Type: image/jpeg" CRLF
* "Content-Range: bytes "
*/
//獲取區間的公共頭部
b = ngx_calloc_buf(r->pool);
if (b == NULL) {
return NGX_ERROR;
}
b->memory = 1;
b->pos = ctx->boundary_header.data;
b->last = ctx->boundary_header.data + ctx->boundary_header.len;
hcl = ngx_alloc_chain_link(r->pool);
if (hcl == NULL) {
return NGX_ERROR;
}
hcl->buf = b;
/* "SSSS-EEEE/TTTT" CRLF CRLF */
//獲取區間的開始、結束位置
b = ngx_calloc_buf(r->pool);
if (b == NULL) {
return NGX_ERROR;
}
b->temporary = 1;
b->pos = range[i].content_range.data;
b->last = range[i].content_range.data + range[i].content_range.len;
rcl = ngx_alloc_chain_link(r->pool);
if (rcl == NULL) {
return NGX_ERROR;
}
rcl->buf = b;
/* the range data */
b = ngx_calloc_buf(r->pool);
if (b == NULL) {
return NGX_ERROR;
}
b->in_file = buf->in_file;
b->temporary = buf->temporary;
b->memory = buf->memory;
b->mmap = buf->mmap;
b->file = buf->file;
//將上面的三個結點構成連結串列
if (buf->in_file) {
b->file_pos = buf->file_pos + range[i].start;
b->file_last = buf->file_pos + range[i].end;
}
if (ngx_buf_in_memory(buf)) {
b->pos = buf->pos + (size_t) range[i].start;
b->last = buf->pos + (size_t) range[i].end;
}
dcl = ngx_alloc_chain_link(r->pool);
if (dcl == NULL) {
return NGX_ERROR;
}
dcl->buf = b;
//將上面的三個結點構成連結串列
*ll = hcl;
hcl->next = rcl;
rcl->next = dcl;
ll = &dcl->next;
}
/* the last boundary CRLF "--0123456789--" CRLF */
//所有塊的結束內容,例如:--00000000005--
//也就是最後一個連結串列節點
b = ngx_calloc_buf(r->pool);
if (b == NULL) {
return NGX_ERROR;
}
b->temporary = 1;
b->last_buf = 1;
b->pos = ngx_pnalloc(r->pool, sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN
+ sizeof("--" CRLF) - 1);
if (b->pos == NULL) {
return NGX_ERROR;
}
b->last = ngx_cpymem(b->pos, ctx->boundary_header.data,
sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN);
*b->last++ = '-'; *b->last++ = '-';
*b->last++ = CR; *b->last++ = LF;
hcl = ngx_alloc_chain_link(r->pool);
if (hcl == NULL) {
return NGX_ERROR;
}
hcl->buf = b;
hcl->next = NULL;
*ll = hcl;
return ngx_http_next_body_filter(r, out);
}
相關文章
- Zepto原始碼分析之form模組原始碼ORM
- Swoole 原始碼分析——Client模組之Recv原始碼client
- Swoole 原始碼分析——Client模組之Send原始碼client
- Django(49)drf解析模組原始碼分析Django原始碼
- Django(51)drf渲染模組原始碼分析Django原始碼
- 從原始碼分析Node的Cluster模組原始碼
- Hadoop2原始碼分析-HDFS核心模組分析Hadoop原始碼
- (一) Mybatis原始碼分析-解析器模組MyBatis原始碼
- Swoole 原始碼分析——Client模組之Connect原始碼client
- Swoole 原始碼分析——Server模組之OpenSSL (上)原始碼Server
- JavaScript 模組化及 SeaJs 原始碼分析JavaScriptJS原始碼
- Django(48)drf請求模組原始碼分析Django原始碼
- mybaits原始碼分析--日誌模組(四)AI原始碼
- mybaits原始碼分析--快取模組(六)AI原始碼快取
- mybaits原始碼分析--binding模組(五)AI原始碼
- Swoole 原始碼分析——Server 模組之 OpenSSL (上)原始碼Server
- Swoole 原始碼分析——Server 模組之 OpenSSL (下)原始碼Server
- beego cache模組原始碼分析筆記四Go原始碼筆記
- Swoole 原始碼分析——Reactor 模組之 ReactorEpoll原始碼React
- 比特幣原始碼分析:VersionBits模組解析比特幣原始碼
- Swoole 原始碼分析——鎖與訊號量模組原始碼
- Swoole 原始碼分析——基礎模組之 Pipe 管道原始碼
- skynet原始碼分析(1)--模組載入原始碼
- btcpool礦池原始碼分析(3)-BlockMaker模組解析TCP原始碼BloC
- btcpool礦池原始碼分析(5)-JobMaker模組解析TCP原始碼
- btcpool礦池原始碼分析(6)-nmcauxmaker模組解析TCP原始碼UX
- btcpool礦池原始碼分析(9)-statshttpd模組解析TCP原始碼httpd
- btcpool礦池原始碼分析(10)-StratumServer模組解析TCP原始碼Server
- mybaits原始碼分析--型別轉換模組(三)AI原始碼型別
- btcpool礦池原始碼分析(4)-GbtMaker模組解析TCP原始碼
- btcpool礦池原始碼分析(6)-PoolWatcher模組解析TCP原始碼
- jQuery1.9.1原始碼分析--資料快取Data模組jQuery原始碼快取
- Retrofit原始碼分析三 原始碼分析原始碼
- 集合原始碼分析[2]-AbstractList 原始碼分析原始碼
- 集合原始碼分析[1]-Collection 原始碼分析原始碼
- 集合原始碼分析[3]-ArrayList 原始碼分析原始碼
- Guava 原始碼分析之 EventBus 原始碼分析Guava原始碼
- Swoole 原始碼分析——Server模組之ReactorThread事件迴圈(下)原始碼ServerReactthread事件