comparison src/http/modules/ngx_http_gunzip_filter_module.c @ 686:2e8a942c8872 NGINX_1_3_6

nginx 1.3.6 *) Feature: the ngx_http_gunzip_filter_module. *) Feature: the "memcached_gzip_flag" directive. *) Feature: the "always" parameter of the "gzip_static" directive. *) Bugfix: in the "limit_req" directive; the bug had appeared in 1.1.14. Thanks to Charles Chen. *) Bugfix: nginx could not be built by gcc 4.7 with -O2 optimization if the --with-ipv6 option was used.
author Igor Sysoev <http://sysoev.ru>
date Wed, 12 Sep 2012 00:00:00 +0400
parents
children b5b7eea22fda
comparison
equal deleted inserted replaced
685:0a9f545d4f4b 686:2e8a942c8872
1
2 /*
3 * Copyright (C) Igor Sysoev
4 * Copyright (C) Maxim Dounin
5 * Copyright (C) Nginx, Inc.
6 */
7
8
9 #include <ngx_config.h>
10 #include <ngx_core.h>
11 #include <ngx_http.h>
12
13 #include <zlib.h>
14
15
16 typedef struct {
17 ngx_flag_t enable;
18 ngx_bufs_t bufs;
19 } ngx_http_gunzip_conf_t;
20
21
22 typedef struct {
23 ngx_chain_t *in;
24 ngx_chain_t *free;
25 ngx_chain_t *busy;
26 ngx_chain_t *out;
27 ngx_chain_t **last_out;
28
29 ngx_buf_t *in_buf;
30 ngx_buf_t *out_buf;
31 ngx_int_t bufs;
32
33 unsigned started:1;
34 unsigned flush:4;
35 unsigned redo:1;
36 unsigned done:1;
37 unsigned nomem:1;
38
39 z_stream zstream;
40 ngx_http_request_t *request;
41 } ngx_http_gunzip_ctx_t;
42
43
44 static ngx_int_t ngx_http_gunzip_filter_inflate_start(ngx_http_request_t *r,
45 ngx_http_gunzip_ctx_t *ctx);
46 static ngx_int_t ngx_http_gunzip_filter_add_data(ngx_http_request_t *r,
47 ngx_http_gunzip_ctx_t *ctx);
48 static ngx_int_t ngx_http_gunzip_filter_get_buf(ngx_http_request_t *r,
49 ngx_http_gunzip_ctx_t *ctx);
50 static ngx_int_t ngx_http_gunzip_filter_inflate(ngx_http_request_t *r,
51 ngx_http_gunzip_ctx_t *ctx);
52 static ngx_int_t ngx_http_gunzip_filter_inflate_end(ngx_http_request_t *r,
53 ngx_http_gunzip_ctx_t *ctx);
54
55 static void *ngx_http_gunzip_filter_alloc(void *opaque, u_int items,
56 u_int size);
57 static void ngx_http_gunzip_filter_free(void *opaque, void *address);
58
59 static ngx_int_t ngx_http_gunzip_filter_init(ngx_conf_t *cf);
60 static void *ngx_http_gunzip_create_conf(ngx_conf_t *cf);
61 static char *ngx_http_gunzip_merge_conf(ngx_conf_t *cf,
62 void *parent, void *child);
63
64
65 static ngx_command_t ngx_http_gunzip_filter_commands[] = {
66
67 { ngx_string("gunzip"),
68 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
69 ngx_conf_set_flag_slot,
70 NGX_HTTP_LOC_CONF_OFFSET,
71 offsetof(ngx_http_gunzip_conf_t, enable),
72 NULL },
73
74 { ngx_string("gunzip_buffers"),
75 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
76 ngx_conf_set_bufs_slot,
77 NGX_HTTP_LOC_CONF_OFFSET,
78 offsetof(ngx_http_gunzip_conf_t, bufs),
79 NULL },
80
81 ngx_null_command
82 };
83
84
85 static ngx_http_module_t ngx_http_gunzip_filter_module_ctx = {
86 NULL, /* preconfiguration */
87 ngx_http_gunzip_filter_init, /* postconfiguration */
88
89 NULL, /* create main configuration */
90 NULL, /* init main configuration */
91
92 NULL, /* create server configuration */
93 NULL, /* merge server configuration */
94
95 ngx_http_gunzip_create_conf, /* create location configuration */
96 ngx_http_gunzip_merge_conf /* merge location configuration */
97 };
98
99
100 ngx_module_t ngx_http_gunzip_filter_module = {
101 NGX_MODULE_V1,
102 &ngx_http_gunzip_filter_module_ctx, /* module context */
103 ngx_http_gunzip_filter_commands, /* module directives */
104 NGX_HTTP_MODULE, /* module type */
105 NULL, /* init master */
106 NULL, /* init module */
107 NULL, /* init process */
108 NULL, /* init thread */
109 NULL, /* exit thread */
110 NULL, /* exit process */
111 NULL, /* exit master */
112 NGX_MODULE_V1_PADDING
113 };
114
115
116 static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
117 static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
118
119
120 static ngx_int_t
121 ngx_http_gunzip_header_filter(ngx_http_request_t *r)
122 {
123 ngx_http_gunzip_ctx_t *ctx;
124 ngx_http_gunzip_conf_t *conf;
125
126 conf = ngx_http_get_module_loc_conf(r, ngx_http_gunzip_filter_module);
127
128 /* TODO support multiple content-codings */
129 /* TODO always gunzip - due to configuration or module request */
130 /* TODO ignore content encoding? */
131
132 if (!conf->enable
133 || r->headers_out.content_encoding == NULL
134 || r->headers_out.content_encoding->value.len != 4
135 || ngx_strncasecmp(r->headers_out.content_encoding->value.data,
136 (u_char *) "gzip", 4) != 0)
137 {
138 return ngx_http_next_header_filter(r);
139 }
140
141 r->gzip_vary = 1;
142
143 if (!r->gzip_tested) {
144 if (ngx_http_gzip_ok(r) == NGX_OK) {
145 return ngx_http_next_header_filter(r);
146 }
147
148 } else if (!r->gzip_ok) {
149 return ngx_http_next_header_filter(r);
150 }
151
152 ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_gunzip_ctx_t));
153 if (ctx == NULL) {
154 return NGX_ERROR;
155 }
156
157 ngx_http_set_ctx(r, ctx, ngx_http_gunzip_filter_module);
158
159 ctx->request = r;
160
161 r->filter_need_in_memory = 1;
162
163 r->headers_out.content_encoding->hash = 0;
164 r->headers_out.content_encoding = NULL;
165
166 ngx_http_clear_content_length(r);
167 ngx_http_clear_accept_ranges(r);
168
169 return ngx_http_next_header_filter(r);
170 }
171
172
173 static ngx_int_t
174 ngx_http_gunzip_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
175 {
176 int rc;
177 ngx_chain_t *cl;
178 ngx_http_gunzip_ctx_t *ctx;
179
180 ctx = ngx_http_get_module_ctx(r, ngx_http_gunzip_filter_module);
181
182 if (ctx == NULL || ctx->done) {
183 return ngx_http_next_body_filter(r, in);
184 }
185
186 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
187 "http gunzip filter");
188
189 if (!ctx->started) {
190 if (ngx_http_gunzip_filter_inflate_start(r, ctx) != NGX_OK) {
191 goto failed;
192 }
193 }
194
195 if (in) {
196 if (ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) {
197 goto failed;
198 }
199 }
200
201 if (ctx->nomem) {
202
203 /* flush busy buffers */
204
205 if (ngx_http_next_body_filter(r, NULL) == NGX_ERROR) {
206 goto failed;
207 }
208
209 cl = NULL;
210
211 ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &cl,
212 (ngx_buf_tag_t) &ngx_http_gunzip_filter_module);
213 ctx->nomem = 0;
214 }
215
216 for ( ;; ) {
217
218 /* cycle while we can write to a client */
219
220 for ( ;; ) {
221
222 /* cycle while there is data to feed zlib and ... */
223
224 rc = ngx_http_gunzip_filter_add_data(r, ctx);
225
226 if (rc == NGX_DECLINED) {
227 break;
228 }
229
230 if (rc == NGX_AGAIN) {
231 continue;
232 }
233
234
235 /* ... there are buffers to write zlib output */
236
237 rc = ngx_http_gunzip_filter_get_buf(r, ctx);
238
239 if (rc == NGX_DECLINED) {
240 break;
241 }
242
243 if (rc == NGX_ERROR) {
244 goto failed;
245 }
246
247 rc = ngx_http_gunzip_filter_inflate(r, ctx);
248
249 if (rc == NGX_OK) {
250 break;
251 }
252
253 if (rc == NGX_ERROR) {
254 goto failed;
255 }
256
257 /* rc == NGX_AGAIN */
258 }
259
260 if (ctx->out == NULL) {
261 return ctx->busy ? NGX_AGAIN : NGX_OK;
262 }
263
264 rc = ngx_http_next_body_filter(r, ctx->out);
265
266 if (rc == NGX_ERROR) {
267 goto failed;
268 }
269
270 ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &ctx->out,
271 (ngx_buf_tag_t) &ngx_http_gunzip_filter_module);
272 ctx->last_out = &ctx->out;
273
274 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
275 "gunzip out: %p", ctx->out);
276
277 ctx->nomem = 0;
278
279 if (ctx->done) {
280 return rc;
281 }
282 }
283
284 /* unreachable */
285
286 failed:
287
288 ctx->done = 1;
289
290 return NGX_ERROR;
291 }
292
293
294 static ngx_int_t
295 ngx_http_gunzip_filter_inflate_start(ngx_http_request_t *r,
296 ngx_http_gunzip_ctx_t *ctx)
297 {
298 int rc;
299
300 ctx->zstream.next_in = Z_NULL;
301 ctx->zstream.avail_in = 0;
302
303 ctx->zstream.zalloc = ngx_http_gunzip_filter_alloc;
304 ctx->zstream.zfree = ngx_http_gunzip_filter_free;
305 ctx->zstream.opaque = ctx;
306
307 /* windowBits +16 to decode gzip, zlib 1.2.0.4+ */
308 rc = inflateInit2(&ctx->zstream, MAX_WBITS + 16);
309
310 if (rc != Z_OK) {
311 ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
312 "inflateInit2() failed: %d", rc);
313 return NGX_ERROR;
314 }
315
316 ctx->started = 1;
317
318 ctx->last_out = &ctx->out;
319 ctx->flush = Z_NO_FLUSH;
320
321 return NGX_OK;
322 }
323
324
325 static ngx_int_t
326 ngx_http_gunzip_filter_add_data(ngx_http_request_t *r,
327 ngx_http_gunzip_ctx_t *ctx)
328 {
329 if (ctx->zstream.avail_in || ctx->flush != Z_NO_FLUSH || ctx->redo) {
330 return NGX_OK;
331 }
332
333 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
334 "gunzip in: %p", ctx->in);
335
336 if (ctx->in == NULL) {
337 return NGX_DECLINED;
338 }
339
340 ctx->in_buf = ctx->in->buf;
341 ctx->in = ctx->in->next;
342
343 ctx->zstream.next_in = ctx->in_buf->pos;
344 ctx->zstream.avail_in = ctx->in_buf->last - ctx->in_buf->pos;
345
346 ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
347 "gunzip in_buf:%p ni:%p ai:%ud",
348 ctx->in_buf,
349 ctx->zstream.next_in, ctx->zstream.avail_in);
350
351 if (ctx->in_buf->last_buf || ctx->in_buf->last_in_chain) {
352 ctx->flush = Z_FINISH;
353
354 } else if (ctx->in_buf->flush) {
355 ctx->flush = Z_SYNC_FLUSH;
356
357 } else if (ctx->zstream.avail_in == 0) {
358 /* ctx->flush == Z_NO_FLUSH */
359 return NGX_AGAIN;
360 }
361
362 return NGX_OK;
363 }
364
365
366 static ngx_int_t
367 ngx_http_gunzip_filter_get_buf(ngx_http_request_t *r,
368 ngx_http_gunzip_ctx_t *ctx)
369 {
370 ngx_http_gunzip_conf_t *conf;
371
372 if (ctx->zstream.avail_out) {
373 return NGX_OK;
374 }
375
376 conf = ngx_http_get_module_loc_conf(r, ngx_http_gunzip_filter_module);
377
378 if (ctx->free) {
379 ctx->out_buf = ctx->free->buf;
380 ctx->free = ctx->free->next;
381
382 ctx->out_buf->flush = 0;
383
384 } else if (ctx->bufs < conf->bufs.num) {
385
386 ctx->out_buf = ngx_create_temp_buf(r->pool, conf->bufs.size);
387 if (ctx->out_buf == NULL) {
388 return NGX_ERROR;
389 }
390
391 ctx->out_buf->tag = (ngx_buf_tag_t) &ngx_http_gunzip_filter_module;
392 ctx->out_buf->recycled = 1;
393 ctx->bufs++;
394
395 } else {
396 ctx->nomem = 1;
397 return NGX_DECLINED;
398 }
399
400 ctx->zstream.next_out = ctx->out_buf->pos;
401 ctx->zstream.avail_out = conf->bufs.size;
402
403 return NGX_OK;
404 }
405
406
407 static ngx_int_t
408 ngx_http_gunzip_filter_inflate(ngx_http_request_t *r,
409 ngx_http_gunzip_ctx_t *ctx)
410 {
411 int rc;
412 ngx_buf_t *b;
413 ngx_chain_t *cl;
414
415 ngx_log_debug6(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
416 "inflate in: ni:%p no:%p ai:%ud ao:%ud fl:%d redo:%d",
417 ctx->zstream.next_in, ctx->zstream.next_out,
418 ctx->zstream.avail_in, ctx->zstream.avail_out,
419 ctx->flush, ctx->redo);
420
421 rc = inflate(&ctx->zstream, ctx->flush);
422
423 if (rc != Z_OK && rc != Z_STREAM_END && rc != Z_BUF_ERROR) {
424 ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
425 "inflate() failed: %d, %d", ctx->flush, rc);
426 return NGX_ERROR;
427 }
428
429 ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
430 "inflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d",
431 ctx->zstream.next_in, ctx->zstream.next_out,
432 ctx->zstream.avail_in, ctx->zstream.avail_out,
433 rc);
434
435 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
436 "gunzip in_buf:%p pos:%p",
437 ctx->in_buf, ctx->in_buf->pos);
438
439 if (ctx->zstream.next_in) {
440 ctx->in_buf->pos = ctx->zstream.next_in;
441
442 if (ctx->zstream.avail_in == 0) {
443 ctx->zstream.next_in = NULL;
444 }
445 }
446
447 ctx->out_buf->last = ctx->zstream.next_out;
448
449 if (ctx->zstream.avail_out == 0) {
450
451 /* zlib wants to output some more data */
452
453 cl = ngx_alloc_chain_link(r->pool);
454 if (cl == NULL) {
455 return NGX_ERROR;
456 }
457
458 cl->buf = ctx->out_buf;
459 cl->next = NULL;
460 *ctx->last_out = cl;
461 ctx->last_out = &cl->next;
462
463 ctx->redo = 1;
464
465 return NGX_AGAIN;
466 }
467
468 ctx->redo = 0;
469
470 if (ctx->flush == Z_SYNC_FLUSH) {
471
472 ctx->flush = Z_NO_FLUSH;
473
474 cl = ngx_alloc_chain_link(r->pool);
475 if (cl == NULL) {
476 return NGX_ERROR;
477 }
478
479 b = ctx->out_buf;
480
481 if (ngx_buf_size(b) == 0) {
482
483 b = ngx_calloc_buf(ctx->request->pool);
484 if (b == NULL) {
485 return NGX_ERROR;
486 }
487
488 } else {
489 ctx->zstream.avail_out = 0;
490 }
491
492 b->flush = 1;
493
494 cl->buf = b;
495 cl->next = NULL;
496 *ctx->last_out = cl;
497 ctx->last_out = &cl->next;
498
499 return NGX_OK;
500 }
501
502 if (rc == Z_STREAM_END && ctx->flush == Z_FINISH
503 && ctx->zstream.avail_in == 0)
504 {
505
506 if (ngx_http_gunzip_filter_inflate_end(r, ctx) != NGX_OK) {
507 return NGX_ERROR;
508 }
509
510 return NGX_OK;
511 }
512
513 if (rc == Z_STREAM_END && ctx->zstream.avail_in > 0) {
514
515 rc = inflateReset(&ctx->zstream);
516
517 if (rc != Z_OK) {
518 ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
519 "inflateReset() failed: %d", rc);
520 return NGX_ERROR;
521 }
522
523 ctx->redo = 1;
524
525 return NGX_AGAIN;
526 }
527
528 if (ctx->in == NULL) {
529
530 b = ctx->out_buf;
531
532 if (ngx_buf_size(b) == 0) {
533 return NGX_OK;
534 }
535
536 cl = ngx_alloc_chain_link(r->pool);
537 if (cl == NULL) {
538 return NGX_ERROR;
539 }
540
541 ctx->zstream.avail_out = 0;
542
543 cl->buf = b;
544 cl->next = NULL;
545 *ctx->last_out = cl;
546 ctx->last_out = &cl->next;
547
548 return NGX_OK;
549 }
550
551 return NGX_AGAIN;
552 }
553
554
555 static ngx_int_t
556 ngx_http_gunzip_filter_inflate_end(ngx_http_request_t *r,
557 ngx_http_gunzip_ctx_t *ctx)
558 {
559 int rc;
560 ngx_buf_t *b;
561 ngx_chain_t *cl;
562
563 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
564 "gunzip inflate end");
565
566 rc = inflateEnd(&ctx->zstream);
567
568 if (rc != Z_OK) {
569 ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
570 "inflateEnd() failed: %d", rc);
571 return NGX_ERROR;
572 }
573
574 b = ctx->out_buf;
575
576 if (ngx_buf_size(b) == 0) {
577
578 b = ngx_calloc_buf(ctx->request->pool);
579 if (b == NULL) {
580 return NGX_ERROR;
581 }
582 }
583
584 cl = ngx_alloc_chain_link(r->pool);
585 if (cl == NULL) {
586 return NGX_ERROR;
587 }
588
589 cl->buf = b;
590 cl->next = NULL;
591 *ctx->last_out = cl;
592 ctx->last_out = &cl->next;
593
594 b->last_buf = (r == r->main) ? 1 : 0;
595 b->last_in_chain = 1;
596 b->sync = 1;
597
598 ctx->done = 1;
599
600 return NGX_OK;
601 }
602
603
604 static void *
605 ngx_http_gunzip_filter_alloc(void *opaque, u_int items, u_int size)
606 {
607 ngx_http_gunzip_ctx_t *ctx = opaque;
608
609 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
610 "gunzip alloc: n:%ud s:%ud",
611 items, size);
612
613 return ngx_palloc(ctx->request->pool, items * size);
614 }
615
616
617 static void
618 ngx_http_gunzip_filter_free(void *opaque, void *address)
619 {
620 #if 0
621 ngx_http_gunzip_ctx_t *ctx = opaque;
622
623 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
624 "gunzip free: %p", address);
625 #endif
626 }
627
628
629 static void *
630 ngx_http_gunzip_create_conf(ngx_conf_t *cf)
631 {
632 ngx_http_gunzip_conf_t *conf;
633
634 conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_gunzip_conf_t));
635 if (conf == NULL) {
636 return NULL;
637 }
638
639 /*
640 * set by ngx_pcalloc():
641 *
642 * conf->bufs.num = 0;
643 */
644
645 conf->enable = NGX_CONF_UNSET;
646
647 return conf;
648 }
649
650
651 static char *
652 ngx_http_gunzip_merge_conf(ngx_conf_t *cf, void *parent, void *child)
653 {
654 ngx_http_gunzip_conf_t *prev = parent;
655 ngx_http_gunzip_conf_t *conf = child;
656
657 ngx_conf_merge_value(conf->enable, prev->enable, 0);
658
659 ngx_conf_merge_bufs_value(conf->bufs, prev->bufs,
660 (128 * 1024) / ngx_pagesize, ngx_pagesize);
661
662 return NGX_CONF_OK;
663 }
664
665
666 static ngx_int_t
667 ngx_http_gunzip_filter_init(ngx_conf_t *cf)
668 {
669 ngx_http_next_header_filter = ngx_http_top_header_filter;
670 ngx_http_top_header_filter = ngx_http_gunzip_header_filter;
671
672 ngx_http_next_body_filter = ngx_http_top_body_filter;
673 ngx_http_top_body_filter = ngx_http_gunzip_body_filter;
674
675 return NGX_OK;
676 }