Mercurial > hg > nginx-ranges
annotate src/http/modules/ngx_http_range_filter_module.c @ 390:a5f67d82aea3
Range filter: support for multiple buffers (single range only).
This partially allows range support for replies that aren't sent in single
buffer. More work still required to support multipart/byteranges replies
(when client asks for multiple ranges in one request).
author | Maxim Dounin <mdounin@mdounin.ru> |
---|---|
date | Mon, 21 Jul 2008 05:31:34 +0400 |
parents | 984bb0b1399b |
children | 1d9bef53cd8e |
rev | line source |
---|---|
50 | 1 |
2 /* | |
3 * Copyright (C) Igor Sysoev | |
4 */ | |
5 | |
6 | |
7 #include <ngx_config.h> | |
8 #include <ngx_core.h> | |
9 #include <ngx_http.h> | |
10 | |
11 | |
12 /* | |
13 * the single part format: | |
14 * | |
15 * "HTTP/1.0 206 Partial Content" CRLF | |
16 * ... header ... | |
17 * "Content-Type: image/jpeg" CRLF | |
18 * "Content-Length: SIZE" CRLF | |
19 * "Content-Range: bytes START-END/SIZE" CRLF | |
20 * CRLF | |
21 * ... data ... | |
22 * | |
23 * | |
24 * the mutlipart format: | |
25 * | |
26 * "HTTP/1.0 206 Partial Content" CRLF | |
27 * ... header ... | |
28 * "Content-Type: multipart/byteranges; boundary=0123456789" CRLF | |
29 * CRLF | |
30 * CRLF | |
31 * "--0123456789" CRLF | |
32 * "Content-Type: image/jpeg" CRLF | |
33 * "Content-Range: bytes START0-END0/SIZE" CRLF | |
34 * CRLF | |
35 * ... data ... | |
36 * CRLF | |
37 * "--0123456789" CRLF | |
38 * "Content-Type: image/jpeg" CRLF | |
39 * "Content-Range: bytes START1-END1/SIZE" CRLF | |
40 * CRLF | |
41 * ... data ... | |
42 * CRLF | |
43 * "--0123456789--" CRLF | |
44 */ | |
45 | |
46 | |
47 typedef struct { | |
272 | 48 off_t start; |
49 off_t end; | |
50 ngx_str_t content_range; | |
51 } ngx_http_range_t; | |
52 | |
53 | |
54 typedef struct { | |
55 off_t offset; | |
56 ngx_str_t boundary_header; | |
57 ngx_array_t ranges; | |
50 | 58 } ngx_http_range_filter_ctx_t; |
59 | |
60 | |
230 | 61 static ngx_int_t ngx_http_range_header_filter_init(ngx_conf_t *cf); |
62 static ngx_int_t ngx_http_range_body_filter_init(ngx_conf_t *cf); | |
50 | 63 |
64 | |
65 static ngx_http_module_t ngx_http_range_header_filter_module_ctx = { | |
58 | 66 NULL, /* preconfiguration */ |
230 | 67 ngx_http_range_header_filter_init, /* postconfiguration */ |
50 | 68 |
69 NULL, /* create main configuration */ | |
70 NULL, /* init main configuration */ | |
71 | |
72 NULL, /* create server configuration */ | |
73 NULL, /* merge server configuration */ | |
74 | |
75 NULL, /* create location configuration */ | |
76 NULL, /* merge location configuration */ | |
77 }; | |
78 | |
79 | |
80 ngx_module_t ngx_http_range_header_filter_module = { | |
58 | 81 NGX_MODULE_V1, |
50 | 82 &ngx_http_range_header_filter_module_ctx, /* module context */ |
83 NULL, /* module directives */ | |
84 NGX_HTTP_MODULE, /* module type */ | |
90 | 85 NULL, /* init master */ |
230 | 86 NULL, /* init module */ |
90 | 87 NULL, /* init process */ |
88 NULL, /* init thread */ | |
89 NULL, /* exit thread */ | |
90 NULL, /* exit process */ | |
91 NULL, /* exit master */ | |
92 NGX_MODULE_V1_PADDING | |
50 | 93 }; |
94 | |
95 | |
96 static ngx_http_module_t ngx_http_range_body_filter_module_ctx = { | |
58 | 97 NULL, /* preconfiguration */ |
230 | 98 ngx_http_range_body_filter_init, /* postconfiguration */ |
50 | 99 |
100 NULL, /* create main configuration */ | |
101 NULL, /* init main configuration */ | |
102 | |
103 NULL, /* create server configuration */ | |
104 NULL, /* merge server configuration */ | |
105 | |
106 NULL, /* create location configuration */ | |
107 NULL, /* merge location configuration */ | |
108 }; | |
109 | |
110 | |
111 ngx_module_t ngx_http_range_body_filter_module = { | |
58 | 112 NGX_MODULE_V1, |
50 | 113 &ngx_http_range_body_filter_module_ctx, /* module context */ |
114 NULL, /* module directives */ | |
115 NGX_HTTP_MODULE, /* module type */ | |
90 | 116 NULL, /* init master */ |
230 | 117 NULL, /* init module */ |
90 | 118 NULL, /* init process */ |
119 NULL, /* init thread */ | |
120 NULL, /* exit thread */ | |
121 NULL, /* exit process */ | |
122 NULL, /* exit master */ | |
123 NGX_MODULE_V1_PADDING | |
50 | 124 }; |
125 | |
126 | |
127 static ngx_http_output_header_filter_pt ngx_http_next_header_filter; | |
128 static ngx_http_output_body_filter_pt ngx_http_next_body_filter; | |
129 | |
130 | |
131 static ngx_int_t | |
132 ngx_http_range_header_filter(ngx_http_request_t *r) | |
133 { | |
134 u_char *p; | |
135 size_t len; | |
136 off_t start, end; | |
346 | 137 time_t if_range; |
50 | 138 ngx_int_t rc; |
139 ngx_uint_t suffix, i; | |
140 ngx_atomic_uint_t boundary; | |
141 ngx_table_elt_t *content_range; | |
142 ngx_http_range_t *range; | |
143 ngx_http_range_filter_ctx_t *ctx; | |
144 | |
145 if (r->http_version < NGX_HTTP_VERSION_10 | |
146 || r->headers_out.status != NGX_HTTP_OK | |
146 | 147 || r != r->main |
50 | 148 || r->headers_out.content_length_n == -1 |
130 | 149 || !r->allow_ranges) |
50 | 150 { |
151 return ngx_http_next_header_filter(r); | |
152 } | |
153 | |
154 if (r->headers_in.range == NULL | |
155 || r->headers_in.range->value.len < 7 | |
286 | 156 || ngx_strncasecmp(r->headers_in.range->value.data, |
157 (u_char *) "bytes=", 6) | |
158 != 0) | |
50 | 159 { |
346 | 160 goto next_filter; |
161 } | |
162 | |
163 if (r->headers_in.if_range && r->headers_out.last_modified_time != -1) { | |
164 | |
165 if_range = ngx_http_parse_time(r->headers_in.if_range->value.data, | |
166 r->headers_in.if_range->value.len); | |
50 | 167 |
346 | 168 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
169 "http ir:%d lm:%d", | |
170 if_range, r->headers_out.last_modified_time); | |
50 | 171 |
346 | 172 if (if_range != r->headers_out.last_modified_time) { |
173 goto next_filter; | |
174 } | |
50 | 175 } |
176 | |
272 | 177 ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_range_filter_ctx_t)); |
178 if (ctx == NULL) { | |
179 return NGX_ERROR; | |
180 } | |
181 | |
182 if (ngx_array_init(&ctx->ranges, r->pool, 1, sizeof(ngx_http_range_t)) | |
183 == NGX_ERROR) | |
50 | 184 { |
185 return NGX_ERROR; | |
186 } | |
187 | |
188 rc = 0; | |
189 range = NULL; | |
190 p = r->headers_in.range->value.data + 6; | |
191 | |
192 for ( ;; ) { | |
193 start = 0; | |
194 end = 0; | |
195 suffix = 0; | |
196 | |
197 while (*p == ' ') { p++; } | |
198 | |
199 if (*p != '-') { | |
200 if (*p < '0' || *p > '9') { | |
201 rc = NGX_HTTP_RANGE_NOT_SATISFIABLE; | |
202 break; | |
203 } | |
204 | |
205 while (*p >= '0' && *p <= '9') { | |
206 start = start * 10 + *p++ - '0'; | |
207 } | |
208 | |
209 while (*p == ' ') { p++; } | |
210 | |
211 if (*p++ != '-') { | |
212 rc = NGX_HTTP_RANGE_NOT_SATISFIABLE; | |
213 break; | |
214 } | |
215 | |
216 if (start >= r->headers_out.content_length_n) { | |
217 rc = NGX_HTTP_RANGE_NOT_SATISFIABLE; | |
218 break; | |
219 } | |
220 | |
221 while (*p == ' ') { p++; } | |
222 | |
223 if (*p == ',' || *p == '\0') { | |
272 | 224 range = ngx_array_push(&ctx->ranges); |
50 | 225 if (range == NULL) { |
226 return NGX_ERROR; | |
227 } | |
228 | |
229 range->start = start; | |
230 range->end = r->headers_out.content_length_n; | |
231 | |
232 if (*p++ != ',') { | |
233 break; | |
234 } | |
235 | |
236 continue; | |
237 } | |
238 | |
239 } else { | |
240 suffix = 1; | |
241 p++; | |
242 } | |
243 | |
244 if (*p < '0' || *p > '9') { | |
245 rc = NGX_HTTP_RANGE_NOT_SATISFIABLE; | |
246 break; | |
247 } | |
248 | |
249 while (*p >= '0' && *p <= '9') { | |
250 end = end * 10 + *p++ - '0'; | |
251 } | |
252 | |
253 while (*p == ' ') { p++; } | |
254 | |
255 if (*p != ',' && *p != '\0') { | |
256 rc = NGX_HTTP_RANGE_NOT_SATISFIABLE; | |
257 break; | |
258 } | |
259 | |
260 if (suffix) { | |
261 start = r->headers_out.content_length_n - end; | |
262 end = r->headers_out.content_length_n - 1; | |
263 } | |
264 | |
265 if (start > end) { | |
266 rc = NGX_HTTP_RANGE_NOT_SATISFIABLE; | |
267 break; | |
268 } | |
269 | |
272 | 270 range = ngx_array_push(&ctx->ranges); |
50 | 271 if (range == NULL) { |
272 return NGX_ERROR; | |
273 } | |
274 | |
275 range->start = start; | |
276 | |
277 if (end >= r->headers_out.content_length_n) { | |
278 /* | |
279 * Download Accelerator sends the last byte position | |
280 * that equals to the file length | |
281 */ | |
282 range->end = r->headers_out.content_length_n; | |
283 | |
284 } else { | |
285 range->end = end + 1; | |
286 } | |
287 | |
288 if (*p++ != ',') { | |
289 break; | |
290 } | |
291 } | |
292 | |
293 if (rc) { | |
294 | |
295 /* rc == NGX_HTTP_RANGE_NOT_SATISFIABLE */ | |
296 | |
297 r->headers_out.status = rc; | |
298 | |
299 content_range = ngx_list_push(&r->headers_out.headers); | |
300 if (content_range == NULL) { | |
301 return NGX_ERROR; | |
302 } | |
303 | |
304 r->headers_out.content_range = content_range; | |
305 | |
58 | 306 content_range->hash = 1; |
50 | 307 content_range->key.len = sizeof("Content-Range") - 1; |
308 content_range->key.data = (u_char *) "Content-Range"; | |
309 | |
382 | 310 content_range->value.data = ngx_pnalloc(r->pool, |
50 | 311 sizeof("bytes */") - 1 + NGX_OFF_T_LEN); |
312 if (content_range->value.data == NULL) { | |
313 return NGX_ERROR; | |
314 } | |
315 | |
316 content_range->value.len = ngx_sprintf(content_range->value.data, | |
317 "bytes */%O", | |
318 r->headers_out.content_length_n) | |
319 - content_range->value.data; | |
320 | |
126 | 321 ngx_http_clear_content_length(r); |
50 | 322 |
323 return rc; | |
324 } | |
325 | |
272 | 326 ngx_http_set_ctx(r, ctx, ngx_http_range_body_filter_module); |
327 | |
50 | 328 r->headers_out.status = NGX_HTTP_PARTIAL_CONTENT; |
329 | |
272 | 330 if (ctx->ranges.nelts == 1) { |
50 | 331 |
332 content_range = ngx_list_push(&r->headers_out.headers); | |
333 if (content_range == NULL) { | |
334 return NGX_ERROR; | |
335 } | |
336 | |
337 r->headers_out.content_range = content_range; | |
338 | |
58 | 339 content_range->hash = 1; |
50 | 340 content_range->key.len = sizeof("Content-Range") - 1; |
341 content_range->key.data = (u_char *) "Content-Range"; | |
342 | |
343 content_range->value.data = | |
382 | 344 ngx_pnalloc(r->pool, sizeof("bytes -/") - 1 + 3 * NGX_OFF_T_LEN); |
50 | 345 if (content_range->value.data == NULL) { |
346 return NGX_ERROR; | |
347 } | |
348 | |
349 /* "Content-Range: bytes SSSS-EEEE/TTTT" header */ | |
350 | |
351 content_range->value.len = ngx_sprintf(content_range->value.data, | |
352 "bytes %O-%O/%O", | |
353 range->start, range->end - 1, | |
354 r->headers_out.content_length_n) | |
355 - content_range->value.data; | |
356 | |
357 r->headers_out.content_length_n = range->end - range->start; | |
358 | |
272 | 359 if (r->headers_out.content_length) { |
360 r->headers_out.content_length->hash = 0; | |
361 r->headers_out.content_length = NULL; | |
362 } | |
363 | |
50 | 364 return ngx_http_next_header_filter(r); |
365 } | |
366 | |
367 | |
368 /* TODO: what if no content_type ?? */ | |
369 | |
370 len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN | |
371 + sizeof(CRLF "Content-Type: ") - 1 | |
58 | 372 + r->headers_out.content_type.len |
50 | 373 + sizeof(CRLF "Content-Range: bytes ") - 1; |
374 | |
375 if (r->headers_out.charset.len) { | |
376 len += sizeof("; charset=") - 1 + r->headers_out.charset.len; | |
377 } | |
378 | |
382 | 379 ctx->boundary_header.data = ngx_pnalloc(r->pool, len); |
50 | 380 if (ctx->boundary_header.data == NULL) { |
381 return NGX_ERROR; | |
382 } | |
383 | |
384 boundary = ngx_next_temp_number(0); | |
385 | |
386 /* | |
387 * The boundary header of the range: | |
388 * CRLF | |
389 * "--0123456789" CRLF | |
390 * "Content-Type: image/jpeg" CRLF | |
391 * "Content-Range: bytes " | |
392 */ | |
393 | |
394 if (r->headers_out.charset.len) { | |
395 ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data, | |
396 CRLF "--%0muA" CRLF | |
397 "Content-Type: %V; charset=%V" CRLF | |
398 "Content-Range: bytes ", | |
399 boundary, | |
58 | 400 &r->headers_out.content_type, |
50 | 401 &r->headers_out.charset) |
402 - ctx->boundary_header.data; | |
403 | |
404 r->headers_out.charset.len = 0; | |
405 | |
406 } else { | |
407 ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data, | |
408 CRLF "--%0muA" CRLF | |
409 "Content-Type: %V" CRLF | |
410 "Content-Range: bytes ", | |
411 boundary, | |
58 | 412 &r->headers_out.content_type) |
50 | 413 - ctx->boundary_header.data; |
414 } | |
415 | |
58 | 416 r->headers_out.content_type.data = |
382 | 417 ngx_pnalloc(r->pool, |
418 sizeof("Content-Type: multipart/byteranges; boundary=") - 1 | |
419 + NGX_ATOMIC_T_LEN); | |
50 | 420 |
58 | 421 if (r->headers_out.content_type.data == NULL) { |
50 | 422 return NGX_ERROR; |
423 } | |
424 | |
425 /* "Content-Type: multipart/byteranges; boundary=0123456789" */ | |
426 | |
58 | 427 r->headers_out.content_type.len = |
428 ngx_sprintf(r->headers_out.content_type.data, | |
50 | 429 "multipart/byteranges; boundary=%0muA", |
430 boundary) | |
58 | 431 - r->headers_out.content_type.data; |
50 | 432 |
433 | |
434 /* the size of the last boundary CRLF "--0123456789--" CRLF */ | |
435 | |
436 len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN + sizeof("--" CRLF) - 1; | |
437 | |
272 | 438 range = ctx->ranges.elts; |
439 for (i = 0; i < ctx->ranges.nelts; i++) { | |
50 | 440 |
441 /* the size of the range: "SSSS-EEEE/TTTT" CRLF CRLF */ | |
442 | |
443 range[i].content_range.data = | |
382 | 444 ngx_pnalloc(r->pool, 3 * NGX_OFF_T_LEN + 2 + 4); |
50 | 445 |
446 if (range[i].content_range.data == NULL) { | |
447 return NGX_ERROR; | |
448 } | |
449 | |
450 range[i].content_range.len = ngx_sprintf(range[i].content_range.data, | |
451 "%O-%O/%O" CRLF CRLF, | |
452 range[i].start, range[i].end - 1, | |
453 r->headers_out.content_length_n) | |
454 - range[i].content_range.data; | |
455 | |
456 len += ctx->boundary_header.len + range[i].content_range.len | |
457 + (size_t) (range[i].end - range[i].start); | |
458 } | |
459 | |
460 r->headers_out.content_length_n = len; | |
272 | 461 |
462 if (r->headers_out.content_length) { | |
463 r->headers_out.content_length->hash = 0; | |
464 r->headers_out.content_length = NULL; | |
465 } | |
50 | 466 |
467 return ngx_http_next_header_filter(r); | |
346 | 468 |
469 next_filter: | |
470 | |
471 r->headers_out.accept_ranges = ngx_list_push(&r->headers_out.headers); | |
472 if (r->headers_out.accept_ranges == NULL) { | |
473 return NGX_ERROR; | |
474 } | |
475 | |
476 r->headers_out.accept_ranges->hash = 1; | |
477 r->headers_out.accept_ranges->key.len = sizeof("Accept-Ranges") - 1; | |
478 r->headers_out.accept_ranges->key.data = (u_char *) "Accept-Ranges"; | |
479 r->headers_out.accept_ranges->value.len = sizeof("bytes") - 1; | |
480 r->headers_out.accept_ranges->value.data = (u_char *) "bytes"; | |
481 | |
482 return ngx_http_next_header_filter(r); | |
50 | 483 } |
484 | |
485 | |
486 static ngx_int_t | |
487 ngx_http_range_body_filter(ngx_http_request_t *r, ngx_chain_t *in) | |
488 { | |
272 | 489 off_t start, last; |
490 ngx_buf_t *b, *buf; | |
50 | 491 ngx_uint_t i; |
390
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
492 ngx_chain_t *out, *cl, *hcl, *rcl, *dcl, **ll; |
50 | 493 ngx_http_range_t *range; |
494 ngx_http_range_filter_ctx_t *ctx; | |
495 | |
272 | 496 if (in == NULL) { |
497 return ngx_http_next_body_filter(r, in); | |
498 } | |
499 | |
500 ctx = ngx_http_get_module_ctx(r, ngx_http_range_body_filter_module); | |
501 | |
502 if (ctx == NULL) { | |
503 return ngx_http_next_body_filter(r, in); | |
504 } | |
505 | |
506 buf = in->buf; | |
507 | |
508 if (ngx_buf_special(in->buf)) { | |
50 | 509 return ngx_http_next_body_filter(r, in); |
510 } | |
511 | |
390
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
512 range = ctx->ranges.elts; |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
513 |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
514 if (ctx->ranges.nelts > 1) { |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
515 goto multipart; |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
516 } |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
517 |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
518 /* |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
519 * the optimized version for the responses |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
520 * that are passed in the single buffer |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
521 */ |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
522 |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
523 out = NULL; |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
524 ll = &out; |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
525 |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
526 for (cl = in; cl; cl = cl->next) { |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
527 |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
528 buf = cl->buf; |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
529 |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
530 start = ctx->offset; |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
531 last = ctx->offset + ngx_buf_size(buf); |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
532 |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
533 ctx->offset = last; |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
534 |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
535 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
536 "range body filter: %O-%O", start, last); |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
537 |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
538 if (ngx_buf_special(buf)) { |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
539 /* pass anyway */ |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
540 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
541 "range body filter: pass special"); |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
542 *ll = cl; |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
543 ll = &cl->next; |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
544 continue; |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
545 } |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
546 |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
547 if (range->end <= start || range->start >= last) { |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
548 /* skip buffer */ |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
549 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
550 "range body filter: skip"); |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
551 buf->pos = buf->last; |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
552 continue; |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
553 } |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
554 |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
555 if (range->start > start) { |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
556 if (buf->in_file) { |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
557 buf->file_pos += range->start - start; |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
558 } |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
559 if (ngx_buf_in_memory(buf)) { |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
560 buf->pos += (size_t) (range->start - start); |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
561 } |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
562 } |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
563 |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
564 if (range->end <= last) { |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
565 if (buf->in_file) { |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
566 buf->file_last -= last - range->end; |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
567 } |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
568 if (ngx_buf_in_memory(buf)) { |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
569 buf->last -= (size_t) (last - range->end); |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
570 } |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
571 |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
572 /* we are done */ |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
573 |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
574 buf->last_buf = 1; |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
575 *ll = cl; |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
576 cl->next = NULL; |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
577 |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
578 break; |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
579 } |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
580 |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
581 *ll = cl; |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
582 ll = &cl->next; |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
583 } |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
584 |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
585 if (out == NULL) { |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
586 return NGX_OK; |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
587 } |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
588 |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
589 return ngx_http_next_body_filter(r, out); |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
590 |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
591 multipart: |
a5f67d82aea3
Range filter: support for multiple buffers (single range only).
Maxim Dounin <mdounin@mdounin.ru>
parents:
382
diff
changeset
|
592 |
272 | 593 if (ctx->offset) { |
594 goto overlapped; | |
595 } | |
596 | |
597 if (!buf->last_buf) { | |
598 | |
599 if (buf->in_file) { | |
600 start = buf->file_pos + ctx->offset; | |
601 last = buf->file_last + ctx->offset; | |
602 | |
603 } else { | |
604 start = buf->pos - buf->start + ctx->offset; | |
605 last = buf->last - buf->start + ctx->offset; | |
606 } | |
607 | |
608 for (i = 0; i < ctx->ranges.nelts; i++) { | |
609 if (start > range[i].start || last < range[i].end) { | |
610 goto overlapped; | |
611 } | |
612 } | |
613 } | |
614 | |
615 ctx->offset = ngx_buf_size(buf); | |
616 | |
617 ll = &out; | |
618 | |
619 for (i = 0; i < ctx->ranges.nelts; i++) { | |
50 | 620 |
272 | 621 /* |
622 * The boundary header of the range: | |
623 * CRLF | |
624 * "--0123456789" CRLF | |
625 * "Content-Type: image/jpeg" CRLF | |
626 * "Content-Range: bytes " | |
627 */ | |
50 | 628 |
272 | 629 b = ngx_calloc_buf(r->pool); |
630 if (b == NULL) { | |
631 return NGX_ERROR; | |
632 } | |
50 | 633 |
272 | 634 b->memory = 1; |
635 b->pos = ctx->boundary_header.data; | |
636 b->last = ctx->boundary_header.data + ctx->boundary_header.len; | |
50 | 637 |
272 | 638 hcl = ngx_alloc_chain_link(r->pool); |
639 if (hcl == NULL) { | |
640 return NGX_ERROR; | |
641 } | |
50 | 642 |
272 | 643 hcl->buf = b; |
50 | 644 |
645 | |
272 | 646 /* "SSSS-EEEE/TTTT" CRLF CRLF */ |
50 | 647 |
648 b = ngx_calloc_buf(r->pool); | |
649 if (b == NULL) { | |
650 return NGX_ERROR; | |
651 } | |
652 | |
653 b->temporary = 1; | |
272 | 654 b->pos = range[i].content_range.data; |
655 b->last = range[i].content_range.data + range[i].content_range.len; | |
656 | |
657 rcl = ngx_alloc_chain_link(r->pool); | |
658 if (rcl == NULL) { | |
659 return NGX_ERROR; | |
660 } | |
661 | |
662 rcl->buf = b; | |
663 | |
664 | |
665 /* the range data */ | |
666 | |
667 b = ngx_calloc_buf(r->pool); | |
668 if (b == NULL) { | |
669 return NGX_ERROR; | |
670 } | |
50 | 671 |
272 | 672 b->in_file = buf->in_file; |
673 b->temporary = buf->temporary; | |
674 b->memory = buf->memory; | |
675 b->mmap = buf->mmap; | |
676 b->file = buf->file; | |
677 | |
678 if (buf->in_file) { | |
282 | 679 b->file_pos = range[i].start; |
680 b->file_last = range[i].end; | |
272 | 681 } |
682 | |
683 if (ngx_buf_in_memory(buf)) { | |
282 | 684 b->pos = buf->start + (size_t) range[i].start; |
685 b->last = buf->start + (size_t) range[i].end; | |
272 | 686 } |
687 | |
688 dcl = ngx_alloc_chain_link(r->pool); | |
689 if (dcl == NULL) { | |
50 | 690 return NGX_ERROR; |
691 } | |
692 | |
272 | 693 dcl->buf = b; |
50 | 694 |
695 *ll = hcl; | |
272 | 696 hcl->next = rcl; |
697 rcl->next = dcl; | |
698 ll = &dcl->next; | |
699 } | |
50 | 700 |
272 | 701 /* the last boundary CRLF "--0123456789--" CRLF */ |
702 | |
703 b = ngx_calloc_buf(r->pool); | |
704 if (b == NULL) { | |
705 return NGX_ERROR; | |
706 } | |
707 | |
708 b->temporary = 1; | |
709 b->last_buf = 1; | |
710 | |
382 | 711 b->pos = ngx_pnalloc(r->pool, sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN |
712 + sizeof("--" CRLF) - 1); | |
272 | 713 if (b->pos == NULL) { |
714 return NGX_ERROR; | |
50 | 715 } |
716 | |
300 | 717 b->last = ngx_cpymem(b->pos, ctx->boundary_header.data, |
718 sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN); | |
272 | 719 *b->last++ = '-'; *b->last++ = '-'; |
720 *b->last++ = CR; *b->last++ = LF; | |
721 | |
722 hcl = ngx_alloc_chain_link(r->pool); | |
723 if (hcl == NULL) { | |
724 return NGX_ERROR; | |
725 } | |
50 | 726 |
272 | 727 hcl->buf = b; |
728 hcl->next = NULL; | |
729 | |
730 *ll = hcl; | |
731 | |
732 return ngx_http_next_body_filter(r, out); | |
733 | |
734 overlapped: | |
735 | |
736 ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, | |
737 "range in overlapped buffers"); | |
738 | |
739 return NGX_ERROR; | |
50 | 740 } |
741 | |
742 | |
743 static ngx_int_t | |
230 | 744 ngx_http_range_header_filter_init(ngx_conf_t *cf) |
50 | 745 { |
746 ngx_http_next_header_filter = ngx_http_top_header_filter; | |
747 ngx_http_top_header_filter = ngx_http_range_header_filter; | |
748 | |
749 return NGX_OK; | |
750 } | |
751 | |
752 | |
753 static ngx_int_t | |
230 | 754 ngx_http_range_body_filter_init(ngx_conf_t *cf) |
50 | 755 { |
756 ngx_http_next_body_filter = ngx_http_top_body_filter; | |
757 ngx_http_top_body_filter = ngx_http_range_body_filter; | |
758 | |
759 return NGX_OK; | |
760 } |