comparison src/http/modules/ngx_http_range_filter.c @ 0:f0b350454894 NGINX_0_1_0

nginx 0.1.0 *) The first public version.
author Igor Sysoev <http://sysoev.ru>
date Mon, 04 Oct 2004 00:00:00 +0400
parents
children 4b2dafa26fe2
comparison
equal deleted inserted replaced
-1:000000000000 0:f0b350454894
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 {
48 ngx_str_t boundary_header;
49 } ngx_http_range_filter_ctx_t;
50
51
52 static ngx_int_t ngx_http_range_header_filter_init(ngx_cycle_t *cycle);
53 static ngx_int_t ngx_http_range_body_filter_init(ngx_cycle_t *cycle);
54
55
56 static ngx_http_module_t ngx_http_range_header_filter_module_ctx = {
57 NULL, /* pre conf */
58
59 NULL, /* create main configuration */
60 NULL, /* init main configuration */
61
62 NULL, /* create server configuration */
63 NULL, /* merge server configuration */
64
65 NULL, /* create location configuration */
66 NULL, /* merge location configuration */
67 };
68
69
70 ngx_module_t ngx_http_range_header_filter_module = {
71 NGX_MODULE,
72 &ngx_http_range_header_filter_module_ctx, /* module context */
73 NULL, /* module directives */
74 NGX_HTTP_MODULE, /* module type */
75 ngx_http_range_header_filter_init, /* init module */
76 NULL /* init child */
77 };
78
79
80 static ngx_http_module_t ngx_http_range_body_filter_module_ctx = {
81 NULL, /* pre conf */
82
83 NULL, /* create main configuration */
84 NULL, /* init main configuration */
85
86 NULL, /* create server configuration */
87 NULL, /* merge server configuration */
88
89 NULL, /* create location configuration */
90 NULL, /* merge location configuration */
91 };
92
93
94 ngx_module_t ngx_http_range_body_filter_module = {
95 NGX_MODULE,
96 &ngx_http_range_body_filter_module_ctx, /* module context */
97 NULL, /* module directives */
98 NGX_HTTP_MODULE, /* module type */
99 ngx_http_range_body_filter_init, /* init module */
100 NULL /* init child */
101 };
102
103
104 static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
105 static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
106
107
108 static ngx_int_t ngx_http_range_header_filter(ngx_http_request_t *r)
109 {
110 ngx_int_t rc;
111 ngx_uint_t boundary, suffix, i;
112 u_char *p;
113 size_t len;
114 off_t start, end;
115 ngx_http_range_t *range;
116 ngx_http_range_filter_ctx_t *ctx;
117
118 if (r->http_version < NGX_HTTP_VERSION_10
119 || r->headers_out.status != NGX_HTTP_OK
120 || r->headers_out.content_length_n == -1
121 || !r->filter_allow_ranges)
122 {
123 return ngx_http_next_header_filter(r);
124 }
125
126 if (r->headers_in.range == NULL
127 || r->headers_in.range->value.len < 7
128 || ngx_strncasecmp(r->headers_in.range->value.data, "bytes=", 6) != 0)
129 {
130
131 r->headers_out.accept_ranges = ngx_list_push(&r->headers_out.headers);
132 if (r->headers_out.accept_ranges == NULL) {
133 return NGX_ERROR;
134 }
135
136 r->headers_out.accept_ranges->key.len = sizeof("Accept-Ranges") - 1;
137 r->headers_out.accept_ranges->key.data = (u_char *) "Accept-Ranges";
138 r->headers_out.accept_ranges->value.len = sizeof("bytes") - 1;
139 r->headers_out.accept_ranges->value.data = (u_char *) "bytes";
140
141 return ngx_http_next_header_filter(r);
142 }
143
144 ngx_init_array(r->headers_out.ranges, r->pool, 5, sizeof(ngx_http_range_t),
145 NGX_ERROR);
146
147 rc = 0;
148 range = NULL;
149 p = r->headers_in.range->value.data + 6;
150
151 for ( ;; ) {
152 start = 0;
153 end = 0;
154 suffix = 0;
155
156 while (*p == ' ') { p++; }
157
158 if (*p != '-') {
159 if (*p < '0' || *p > '9') {
160 rc = NGX_HTTP_RANGE_NOT_SATISFIABLE;
161 break;
162 }
163
164 while (*p >= '0' && *p <= '9') {
165 start = start * 10 + *p++ - '0';
166 }
167
168 while (*p == ' ') { p++; }
169
170 if (*p++ != '-') {
171 rc = NGX_HTTP_RANGE_NOT_SATISFIABLE;
172 break;
173 }
174
175 if (start >= r->headers_out.content_length_n) {
176 rc = NGX_HTTP_RANGE_NOT_SATISFIABLE;
177 break;
178 }
179
180 while (*p == ' ') { p++; }
181
182 if (*p == ',' || *p == '\0') {
183 ngx_test_null(range, ngx_push_array(&r->headers_out.ranges),
184 NGX_ERROR);
185 range->start = start;
186 range->end = r->headers_out.content_length_n;
187
188 if (*p++ != ',') {
189 break;
190 }
191
192 continue;
193 }
194
195 } else {
196 suffix = 1;
197 p++;
198 }
199
200 if (*p < '0' || *p > '9') {
201 rc = NGX_HTTP_RANGE_NOT_SATISFIABLE;
202 break;
203 }
204
205 while (*p >= '0' && *p <= '9') {
206 end = end * 10 + *p++ - '0';
207 }
208
209 while (*p == ' ') { p++; }
210
211 if (*p != ',' && *p != '\0') {
212 rc = NGX_HTTP_RANGE_NOT_SATISFIABLE;
213 break;
214 }
215
216 if (suffix) {
217 start = r->headers_out.content_length_n - end;
218 end = r->headers_out.content_length_n - 1;
219 }
220
221 if (start > end) {
222 rc = NGX_HTTP_RANGE_NOT_SATISFIABLE;
223 break;
224 }
225
226 ngx_test_null(range, ngx_push_array(&r->headers_out.ranges), NGX_ERROR);
227 range->start = start;
228
229 if (end >= r->headers_out.content_length_n) {
230 /*
231 * Download Accelerator sends the last byte position
232 * that equals to the file length
233 */
234 range->end = r->headers_out.content_length_n;
235
236 } else {
237 range->end = end + 1;
238 }
239
240 if (*p++ != ',') {
241 break;
242 }
243 }
244
245 if (rc) {
246
247 /* rc == NGX_HTTP_RANGE_NOT_SATISFIABLE */
248
249 r->headers_out.status = rc;
250 r->headers_out.ranges.nelts = 0;
251
252 r->headers_out.content_range = ngx_list_push(&r->headers_out.headers);
253 if (r->headers_out.content_range == NULL) {
254 return NGX_ERROR;
255 }
256
257 r->headers_out.content_range->key.len = sizeof("Content-Range") - 1;
258 r->headers_out.content_range->key.data = (u_char *) "Content-Range";
259
260 r->headers_out.content_range->value.data =
261 ngx_palloc(r->pool, 8 + 20 + 1);
262 if (r->headers_out.content_range->value.data == NULL) {
263 return NGX_ERROR;
264 }
265
266 r->headers_out.content_range->value.len =
267 ngx_snprintf((char *) r->headers_out.content_range->value.data,
268 8 + 20 + 1, "bytes */" OFF_T_FMT,
269 r->headers_out.content_length_n);
270
271 r->headers_out.content_length_n = -1;
272 if (r->headers_out.content_length) {
273 r->headers_out.content_length->key.len = 0;
274 r->headers_out.content_length = NULL;
275 }
276
277 return rc;
278
279 } else {
280 r->headers_out.status = NGX_HTTP_PARTIAL_CONTENT;
281
282 if (r->headers_out.ranges.nelts == 1) {
283
284 r->headers_out.content_range =
285 ngx_list_push(&r->headers_out.headers);
286 if (r->headers_out.content_range == NULL) {
287 return NGX_ERROR;
288 }
289
290 r->headers_out.content_range->key.len = sizeof("Content-Range") - 1;
291 r->headers_out.content_range->key.data = (u_char *) "Content-Range";
292
293 ngx_test_null(r->headers_out.content_range->value.data,
294 ngx_palloc(r->pool, 6 + 20 + 1 + 20 + 1 + 20 + 1),
295 NGX_ERROR);
296
297 /* "Content-Range: bytes SSSS-EEEE/TTTT" header */
298
299 r->headers_out.content_range->value.len =
300 ngx_snprintf((char *)
301 r->headers_out.content_range->value.data,
302 6 + 20 + 1 + 20 + 1 + 20 + 1,
303 "bytes " OFF_T_FMT "-" OFF_T_FMT "/" OFF_T_FMT,
304 range->start, range->end - 1,
305 r->headers_out.content_length_n);
306
307 r->headers_out.content_length_n = range->end - range->start;
308
309 } else {
310
311 #if 0
312 /* TODO: what if no content_type ?? */
313
314 if (!(r->headers_out.content_type =
315 ngx_http_add_header(&r->headers_out, ngx_http_headers_out)))
316 {
317 return NGX_ERROR;
318 }
319 #endif
320
321 ngx_http_create_ctx(r, ctx, ngx_http_range_body_filter_module,
322 sizeof(ngx_http_range_filter_ctx_t), NGX_ERROR);
323
324 len = 4 + 10 + 2 + 14 + r->headers_out.content_type->value.len
325 + 2 + 21 + 1;
326
327 if (r->headers_out.charset.len) {
328 len += 10 + r->headers_out.charset.len;
329 }
330
331 ngx_test_null(ctx->boundary_header.data, ngx_palloc(r->pool, len),
332 NGX_ERROR);
333
334 boundary = ngx_next_temp_number(0);
335
336 /*
337 * The boundary header of the range:
338 * CRLF
339 * "--0123456789" CRLF
340 * "Content-Type: image/jpeg" CRLF
341 * "Content-Range: bytes "
342 */
343
344 if (r->headers_out.charset.len) {
345 ctx->boundary_header.len =
346 ngx_snprintf((char *) ctx->boundary_header.data, len,
347 CRLF "--%010" NGX_UINT_T_FMT CRLF
348 "Content-Type: %s; charset=%s" CRLF
349 "Content-Range: bytes ",
350 boundary,
351 r->headers_out.content_type->value.data,
352 r->headers_out.charset.data);
353
354 r->headers_out.charset.len = 0;
355
356 } else {
357 ctx->boundary_header.len =
358 ngx_snprintf((char *) ctx->boundary_header.data, len,
359 CRLF "--%010" NGX_UINT_T_FMT CRLF
360 "Content-Type: %s" CRLF
361 "Content-Range: bytes ",
362 boundary,
363 r->headers_out.content_type->value.data);
364 }
365
366 ngx_test_null(r->headers_out.content_type->value.data,
367 ngx_palloc(r->pool, 31 + 10 + 1),
368 NGX_ERROR);
369
370 /* "Content-Type: multipart/byteranges; boundary=0123456789" */
371
372 r->headers_out.content_type->value.len =
373 ngx_snprintf((char *)
374 r->headers_out.content_type->value.data,
375 31 + 10 + 1,
376 "multipart/byteranges; boundary=%010"
377 NGX_UINT_T_FMT,
378 boundary);
379
380 /* the size of the last boundary CRLF "--0123456789--" CRLF */
381 len = 4 + 10 + 4;
382
383 range = r->headers_out.ranges.elts;
384 for (i = 0; i < r->headers_out.ranges.nelts; i++) {
385 ngx_test_null(range[i].content_range.data,
386 ngx_palloc(r->pool, 20 + 1 + 20 + 1 + 20 + 5),
387 NGX_ERROR);
388
389 /* the size of the range: "SSSS-EEEE/TTTT" CRLF CRLF */
390
391 range[i].content_range.len =
392 ngx_snprintf((char *) range[i].content_range.data,
393 20 + 1 + 20 + 1 + 20 + 5,
394 OFF_T_FMT "-" OFF_T_FMT "/" OFF_T_FMT CRLF CRLF,
395 range[i].start, range[i].end - 1,
396 r->headers_out.content_length_n);
397
398 len += ctx->boundary_header.len + range[i].content_range.len
399 + (size_t) (range[i].end - range[i].start);
400 }
401
402 r->headers_out.content_length_n = len;
403 r->headers_out.content_length = NULL;
404 }
405 }
406
407 return ngx_http_next_header_filter(r);
408 }
409
410
411 static ngx_int_t ngx_http_range_body_filter(ngx_http_request_t *r,
412 ngx_chain_t *in)
413 {
414 ngx_uint_t i;
415 ngx_buf_t *b;
416 ngx_chain_t *out, *hcl, *rcl, *dcl, **ll;
417 ngx_http_range_t *range;
418 ngx_http_range_filter_ctx_t *ctx;
419
420 if (r->headers_out.ranges.nelts == 0) {
421 return ngx_http_next_body_filter(r, in);
422 }
423
424 /*
425 * the optimized version for the static files only
426 * that are passed in the single file buf
427 */
428
429 if (in && in->buf->in_file && in->buf->last_buf) {
430 range = r->headers_out.ranges.elts;
431
432 if (r->headers_out.ranges.nelts == 1) {
433 in->buf->file_pos = range->start;
434 in->buf->file_last = range->end;
435
436 return ngx_http_next_body_filter(r, in);
437 }
438
439 ctx = ngx_http_get_module_ctx(r, ngx_http_range_body_filter_module);
440 ll = &out;
441
442 for (i = 0; i < r->headers_out.ranges.nelts; i++) {
443
444 /*
445 * The boundary header of the range:
446 * CRLF
447 * "--0123456789" CRLF
448 * "Content-Type: image/jpeg" CRLF
449 * "Content-Range: bytes "
450 */
451
452 ngx_test_null(b, ngx_calloc_buf(r->pool), NGX_ERROR);
453 b->memory = 1;
454 b->pos = ctx->boundary_header.data;
455 b->last = ctx->boundary_header.data + ctx->boundary_header.len;
456
457 ngx_test_null(hcl, ngx_alloc_chain_link(r->pool), NGX_ERROR);
458 hcl->buf = b;
459
460 /* "SSSS-EEEE/TTTT" CRLF CRLF */
461
462 ngx_test_null(b, ngx_calloc_buf(r->pool), NGX_ERROR);
463 b->temporary = 1;
464 b->pos = range[i].content_range.data;
465 b->last = range[i].content_range.data + range[i].content_range.len;
466
467 ngx_test_null(rcl, ngx_alloc_chain_link(r->pool), NGX_ERROR);
468 rcl->buf = b;
469
470 /* the range data */
471
472 ngx_test_null(b, ngx_calloc_buf(r->pool), NGX_ERROR);
473 b->in_file = 1;
474 b->file_pos = range[i].start;
475 b->file_last = range[i].end;
476 b->file = in->buf->file;
477
478 ngx_alloc_link_and_set_buf(dcl, b, r->pool, NGX_ERROR);
479
480 *ll = hcl;
481 hcl->next = rcl;
482 rcl->next = dcl;
483 ll = &dcl->next;
484 }
485
486 /* the last boundary CRLF "--0123456789--" CRLF */
487
488 ngx_test_null(b, ngx_calloc_buf(r->pool), NGX_ERROR);
489 b->temporary = 1;
490 b->last_buf = 1;
491 ngx_test_null(b->pos, ngx_palloc(r->pool, 4 + 10 + 4), NGX_ERROR);
492 b->last = ngx_cpymem(b->pos, ctx->boundary_header.data, 4 + 10);
493 *b->last++ = '-'; *b->last++ = '-';
494 *b->last++ = CR; *b->last++ = LF;
495
496 ngx_alloc_link_and_set_buf(hcl, b, r->pool, NGX_ERROR);
497 *ll = hcl;
498
499 return ngx_http_next_body_filter(r, out);
500 }
501
502 /* TODO: alert */
503
504 return ngx_http_next_body_filter(r, in);
505 }
506
507
508 static ngx_int_t ngx_http_range_header_filter_init(ngx_cycle_t *cycle)
509 {
510 ngx_http_next_header_filter = ngx_http_top_header_filter;
511 ngx_http_top_header_filter = ngx_http_range_header_filter;
512
513 return NGX_OK;
514 }
515
516
517 static ngx_int_t ngx_http_range_body_filter_init(ngx_cycle_t *cycle)
518 {
519 ngx_http_next_body_filter = ngx_http_top_body_filter;
520 ngx_http_top_body_filter = ngx_http_range_body_filter;
521
522 return NGX_OK;
523 }