[mod_deflate] skip deflate if loadavg too high (fixes #1505)
[lighttpd.git] / src / mod_deflate.c
blob99a9a857822fad2fc2930915fd319decf7b23982
1 /* mod_deflate
4 * bug fix on Robert Jakabosky from alphatrade.com's lighttp 1.4.10 mod_deflate patch
6 * Bug fix and new features:
7 * 1) fix loop bug when content-length is bigger than work-block-size*k
9 * -------
11 * lighttpd-1.4.26.mod_deflate.patch from
12 * https://redmine.lighttpd.net/projects/1/wiki/Docs_ModDeflate
14 * -------
16 * Patch further modified in this incarnation.
18 * Note: this patch only handles completed responses (con->file_finished);
19 * this patch does not currently handle streaming dynamic responses,
20 * and therefore also does not worry about Transfer-Encoding: chunked
21 * (or having separate con->output_queue for chunked-encoded output)
22 * (or using separate buffers per connection instead of p->tmp_buf)
23 * (or handling interactions with block buffering and write timeouts)
25 * Bug fix:
26 * - fixed major bug with compressing chunks with offset > 0
27 * x-ref:
28 * "Response breaking in mod_deflate"
29 * https://redmine.lighttpd.net/issues/986
30 * - fix broken (in some cases) chunk accounting in deflate_compress_response()
31 * - fix broken bzip2
32 * x-ref:
33 * "mod_deflate's bzip2 broken by default"
34 * https://redmine.lighttpd.net/issues/2035
35 * - fix mismatch with current chunk interfaces
36 * x-ref:
37 * "Weird things in chunk.c (functions only handling specific cases, unexpected behaviour)"
38 * https://redmine.lighttpd.net/issues/1510
40 * Behavior changes from prior patch:
41 * - deflate.mimetypes must now be configured to enable compression
42 * deflate.mimetypes = ( ) # compress nothing (disabled; default)
43 * deflate.mimetypes = ( "" ) # compress all mimetypes
44 * deflate.mimetypes = ( "text/" ) # compress text/... mimetypes
45 * x-ref:
46 * "mod_deflate enabled by default"
47 * https://redmine.lighttpd.net/issues/1394
48 * - deflate.enabled directive removed (see new behavior of deflate.mimetypes)
49 * - deflate.debug removed (was developer debug trace, not end-user debug)
50 * - deflate.bzip2 replaced with deflate.allowed-encodings (like mod_compress)
51 * x-ref:
52 * "mod_deflate should allow limiting of compression algorithm from the configuration file"
53 * https://redmine.lighttpd.net/issues/996
54 * "mod_compress disabling methods"
55 * https://redmine.lighttpd.net/issues/1773
56 * - deflate.nocompress-url removed since disabling compression for a URL
57 * can now easily be done by setting to a blank list either directive
58 * deflate.accept_encodings = () or deflate.mimetypes = () in a conditional
59 * block, e.g. $HTTP["url"] =~ "....." { deflate.mimetypes = ( ) }
60 * - deflate.sync-flush removed; controlled by con->conf.stream_response_body
61 * (though streaming compression not currently implemented in mod_deflate)
62 * - inactive directives in this patch (since con->file_finished required)
63 * deflate.work-block-size
64 * deflate.output-buffer-size
65 * - remove weak file size check; SIGBUS is trapped, file that shrink will error
66 * x-ref:
67 * "mod_deflate: filesize check is too weak"
68 * https://redmine.lighttpd.net/issues/1512
69 * - change default deflate.min-compress-size from 0 to now be 256
70 * http://webmasters.stackexchange.com/questions/31750/what-is-recommended-minimum-object-size-for-gzip-performance-benefits
71 * Apache 2.4 mod_deflate minimum is 68 bytes
72 * Akamai recommends minimum 860 bytes
73 * Google recommends minimum be somewhere in range between 150 and 1024 bytes
74 * - deflate.max-compress-size new directive (in kb like compress.max_filesize)
75 * - deflate.mem-level removed (too many knobs for little benefit)
76 * - deflate.window-size removed (too many knobs for little benefit)
78 * Future:
79 * - config directives may be changed, renamed, or removed
80 * e.g. A set of reasonable defaults might be chosen
81 * instead of making them configurable.
82 * deflate.min-compress-size
83 * - might add deflate.mimetypes-exclude = ( ... ) for list of mimetypes
84 * to avoid compressing, even if a broader deflate.mimetypes matched,
85 * e.g. to compress all "text/" except "text/special".
86 * - mod_compress and mod_deflate might merge overlapping feature sets
87 * (mod_compress.cache-dir does not yet have an equivalent in mod_deflate)
89 * Implementation notes:
90 * - http_chunk_append_mem() used instead of http_chunk_append_buffer()
91 * so that p->tmp_buf can be large and re-used. This results in an extra copy
92 * of compressed data before data is sent to network, though if the compressed
93 * size is larger than 64k, it ends up being sent to a temporary file on
94 * disk without suffering an extra copy in memory, and without extra chunk
95 * create and destroy. If this is ever changed to give away buffers, then use
96 * a unique hctx->output buffer per hctx; do not reuse p->tmp_buf across
97 * multiple requests being handled in parallel.
99 #include "first.h"
101 #include <sys/types.h>
102 #include <sys/stat.h>
103 #include "sys-mmap.h"
105 #include <fcntl.h>
106 #include <stdlib.h>
107 #include <string.h>
108 #include <errno.h>
109 #include <time.h>
111 #include "base.h"
112 #include "log.h"
113 #include "buffer.h"
114 #include "etag.h"
115 #include "http_chunk.h"
116 #include "response.h"
118 #include "plugin.h"
120 #if defined HAVE_ZLIB_H && defined HAVE_LIBZ
121 # define USE_ZLIB
122 # include <zlib.h>
123 #endif
124 #ifndef Z_DEFAULT_COMPRESSION
125 #define Z_DEFAULT_COMPRESSION -1
126 #endif
127 #ifndef MAX_WBITS
128 #define MAX_WBITS 15
129 #endif
131 #if defined HAVE_BZLIB_H && defined HAVE_LIBBZ2
132 # define USE_BZ2LIB
133 /* we don't need stdio interface */
134 # define BZ_NO_STDIO
135 # include <bzlib.h>
136 #endif
138 #if defined HAVE_SYS_MMAN_H && defined HAVE_MMAP && defined ENABLE_MMAP
139 #define USE_MMAP
141 #include "sys-mmap.h"
142 #include <setjmp.h>
143 #include <signal.h>
145 static volatile int sigbus_jmp_valid;
146 static sigjmp_buf sigbus_jmp;
148 static void sigbus_handler(int sig) {
149 UNUSED(sig);
150 if (sigbus_jmp_valid) siglongjmp(sigbus_jmp, 1);
151 log_failed_assert(__FILE__, __LINE__, "SIGBUS");
153 #endif
155 /* request: accept-encoding */
156 #define HTTP_ACCEPT_ENCODING_IDENTITY BV(0)
157 #define HTTP_ACCEPT_ENCODING_GZIP BV(1)
158 #define HTTP_ACCEPT_ENCODING_DEFLATE BV(2)
159 #define HTTP_ACCEPT_ENCODING_COMPRESS BV(3)
160 #define HTTP_ACCEPT_ENCODING_BZIP2 BV(4)
161 #define HTTP_ACCEPT_ENCODING_X_GZIP BV(5)
162 #define HTTP_ACCEPT_ENCODING_X_BZIP2 BV(6)
164 #define KByte * 1024
165 #define MByte * 1024 KByte
166 #define GByte * 1024 MByte
168 typedef struct {
169 array *mimetypes;
170 int allowed_encodings;
171 unsigned int max_compress_size;
172 unsigned short min_compress_size;
173 unsigned short output_buffer_size;
174 unsigned short work_block_size;
175 unsigned short sync_flush;
176 short compression_level;
177 double max_loadavg;
178 } plugin_config;
180 typedef struct {
181 PLUGIN_DATA;
182 buffer *tmp_buf;
183 array *encodings;
185 plugin_config **config_storage;
186 plugin_config conf;
187 } plugin_data;
189 typedef struct {
190 union {
191 #ifdef USE_ZLIB
192 z_stream z;
193 #endif
194 #ifdef USE_BZ2LIB
195 bz_stream bz;
196 #endif
197 int dummy;
198 } u;
199 off_t bytes_in;
200 off_t bytes_out;
201 chunkqueue *in_queue;
202 buffer *output;
203 plugin_data *plugin_data;
204 int compression_type;
205 } handler_ctx;
207 static handler_ctx *handler_ctx_init() {
208 handler_ctx *hctx;
210 hctx = calloc(1, sizeof(*hctx));
211 hctx->in_queue = chunkqueue_init();
213 return hctx;
216 static void handler_ctx_free(handler_ctx *hctx) {
217 #if 0
218 if (hctx->output != p->tmp_buf) {
219 buffer_free(hctx->output);
221 #endif
222 chunkqueue_free(hctx->in_queue);
223 free(hctx);
226 INIT_FUNC(mod_deflate_init) {
227 plugin_data *p;
229 p = calloc(1, sizeof(*p));
231 p->encodings = array_init();
232 p->tmp_buf = buffer_init();
233 buffer_string_prepare_copy(p->tmp_buf, 64 KByte);
235 return p;
238 FREE_FUNC(mod_deflate_free) {
239 plugin_data *p = p_d;
241 UNUSED(srv);
243 if (!p) return HANDLER_GO_ON;
245 if (p->config_storage) {
246 size_t i;
247 for (i = 0; i < srv->config_context->used; i++) {
248 plugin_config *s = p->config_storage[i];
250 if (!s) continue;
252 array_free(s->mimetypes);
253 free(s);
255 free(p->config_storage);
258 buffer_free(p->tmp_buf);
259 array_free(p->encodings);
261 free(p);
263 return HANDLER_GO_ON;
266 SETDEFAULTS_FUNC(mod_deflate_setdefaults) {
267 plugin_data *p = p_d;
268 size_t i = 0;
270 config_values_t cv[] = {
271 { "deflate.mimetypes", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION },
272 { "deflate.allowed-encodings", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION },
273 { "deflate.max-compress-size", NULL, T_CONFIG_INT, T_CONFIG_SCOPE_CONNECTION },
274 { "deflate.min-compress-size", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION },
275 { "deflate.compression-level", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION },
276 { "deflate.output-buffer-size", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION },
277 { "deflate.work-block-size", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION },
278 { "deflate.max-loadavg", NULL, T_CONFIG_STRING,T_CONFIG_SCOPE_CONNECTION },
279 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
282 p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
284 for (i = 0; i < srv->config_context->used; i++) {
285 plugin_config *s;
287 s = calloc(1, sizeof(plugin_config));
288 s->mimetypes = array_init();
289 s->allowed_encodings = 0;
290 s->max_compress_size = 128*1024; /*(128 MB measured as num KB)*/
291 s->min_compress_size = 256;
292 s->output_buffer_size = 0;
293 s->work_block_size = 2048;
294 s->sync_flush = 0;
295 s->compression_level = -1;
296 s->max_loadavg = 0.0;
298 array_reset(p->encodings); /* temp array for allowed encodings list */
300 cv[0].destination = s->mimetypes;
301 cv[1].destination = p->encodings;
302 cv[2].destination = &(s->max_compress_size);
303 cv[3].destination = &(s->min_compress_size);
304 cv[4].destination = &(s->compression_level);
305 cv[5].destination = &(s->output_buffer_size);
306 cv[6].destination = &(s->work_block_size);
307 cv[7].destination = p->tmp_buf;
308 buffer_string_set_length(p->tmp_buf, 0);
310 p->config_storage[i] = s;
312 if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
313 return HANDLER_ERROR;
316 if ((s->compression_level < 1 || s->compression_level > 9) &&
317 s->compression_level != -1) {
318 log_error_write(srv, __FILE__, __LINE__, "sd",
319 "compression-level must be between 1 and 9:", s->compression_level);
320 return HANDLER_ERROR;
323 if (!buffer_string_is_empty(p->tmp_buf)) {
324 s->max_loadavg = strtod(p->tmp_buf->ptr, NULL);
327 if (p->encodings->used) {
328 size_t j = 0;
329 for (j = 0; j < p->encodings->used; j++) {
330 #if defined(USE_ZLIB) || defined(USE_BZ2LIB)
331 data_string *ds = (data_string *)p->encodings->data[j];
332 #endif
333 #ifdef USE_ZLIB
334 if (NULL != strstr(ds->value->ptr, "gzip"))
335 s->allowed_encodings |= HTTP_ACCEPT_ENCODING_GZIP | HTTP_ACCEPT_ENCODING_X_GZIP;
336 if (NULL != strstr(ds->value->ptr, "x-gzip"))
337 s->allowed_encodings |= HTTP_ACCEPT_ENCODING_X_GZIP;
338 if (NULL != strstr(ds->value->ptr, "deflate"))
339 s->allowed_encodings |= HTTP_ACCEPT_ENCODING_DEFLATE;
341 if (NULL != strstr(ds->value->ptr, "compress"))
342 s->allowed_encodings |= HTTP_ACCEPT_ENCODING_COMPRESS;
344 #endif
345 #ifdef USE_BZ2LIB
346 if (NULL != strstr(ds->value->ptr, "bzip2"))
347 s->allowed_encodings |= HTTP_ACCEPT_ENCODING_BZIP2 | HTTP_ACCEPT_ENCODING_X_BZIP2;
348 if (NULL != strstr(ds->value->ptr, "x-bzip2"))
349 s->allowed_encodings |= HTTP_ACCEPT_ENCODING_X_BZIP2;
350 #endif
352 } else {
353 /* default encodings */
354 #ifdef USE_ZLIB
355 s->allowed_encodings |= HTTP_ACCEPT_ENCODING_GZIP
356 | HTTP_ACCEPT_ENCODING_X_GZIP
357 | HTTP_ACCEPT_ENCODING_DEFLATE;
358 #endif
359 #ifdef USE_BZ2LIB
360 s->allowed_encodings |= HTTP_ACCEPT_ENCODING_BZIP2
361 | HTTP_ACCEPT_ENCODING_X_BZIP2;
362 #endif
365 /* mod_deflate matches mimetype as prefix of Content-Type
366 * so ignore '*' at end of mimetype for end-user flexibility
367 * in specifying trailing wildcard to grouping of mimetypes */
368 for (size_t m = 0; m < s->mimetypes->used; ++m) {
369 buffer *mimetype = ((data_string *)s->mimetypes->data[m])->value;
370 size_t len = buffer_string_length(mimetype);
371 if (len > 2 && mimetype->ptr[len-1] == '*') {
372 buffer_string_set_length(mimetype, len-1);
377 return HANDLER_GO_ON;
382 #if defined(USE_ZLIB) || defined(USE_BZ2LIB)
383 static int stream_http_chunk_append_mem(server *srv, connection *con, handler_ctx *hctx, size_t len) {
384 /* future: might also write stream to hctx temporary file in compressed file cache */
385 return http_chunk_append_mem(srv, con, hctx->output->ptr, len);
387 #endif
390 #ifdef USE_ZLIB
392 static int stream_deflate_init(handler_ctx *hctx) {
393 z_stream * const z = &hctx->u.z;
394 const plugin_data * const p = hctx->plugin_data;
395 z->zalloc = Z_NULL;
396 z->zfree = Z_NULL;
397 z->opaque = Z_NULL;
398 z->total_in = 0;
399 z->total_out = 0;
400 z->next_out = (unsigned char *)hctx->output->ptr;
401 z->avail_out = hctx->output->size;
403 if (Z_OK != deflateInit2(z,
404 p->conf.compression_level > 0
405 ? p->conf.compression_level
406 : Z_DEFAULT_COMPRESSION,
407 Z_DEFLATED,
408 (hctx->compression_type == HTTP_ACCEPT_ENCODING_GZIP)
409 ? (MAX_WBITS | 16) /*(0x10 flags gzip header, trailer)*/
410 : -MAX_WBITS, /*(negate to suppress zlib header)*/
411 8, /* default memLevel */
412 Z_DEFAULT_STRATEGY)) {
413 return -1;
416 return 0;
419 static int stream_deflate_compress(server *srv, connection *con, handler_ctx *hctx, unsigned char *start, off_t st_size) {
420 z_stream * const z = &(hctx->u.z);
421 size_t len;
423 z->next_in = start;
424 z->avail_in = st_size;
425 hctx->bytes_in += st_size;
427 /* compress data */
428 do {
429 if (Z_OK != deflate(z, Z_NO_FLUSH)) return -1;
431 if (z->avail_out == 0 || z->avail_in > 0) {
432 len = hctx->output->size - z->avail_out;
433 hctx->bytes_out += len;
434 stream_http_chunk_append_mem(srv, con, hctx, len);
435 z->next_out = (unsigned char *)hctx->output->ptr;
436 z->avail_out = hctx->output->size;
438 } while (z->avail_in > 0);
440 return 0;
443 static int stream_deflate_flush(server *srv, connection *con, handler_ctx *hctx, int end) {
444 z_stream * const z = &(hctx->u.z);
445 const plugin_data *p = hctx->plugin_data;
446 size_t len;
447 int rc = 0;
448 int done;
450 /* compress data */
451 do {
452 done = 1;
453 if (end) {
454 rc = deflate(z, Z_FINISH);
455 if (rc == Z_OK) {
456 done = 0;
457 } else if (rc != Z_STREAM_END) {
458 return -1;
460 } else {
461 if (p->conf.sync_flush) {
462 rc = deflate(z, Z_SYNC_FLUSH);
463 if (rc != Z_OK) return -1;
464 } else if (z->avail_in > 0) {
465 rc = deflate(z, Z_NO_FLUSH);
466 if (rc != Z_OK) return -1;
470 len = hctx->output->size - z->avail_out;
471 if (z->avail_out == 0 || (len > 0 && (end || p->conf.sync_flush))) {
472 hctx->bytes_out += len;
473 stream_http_chunk_append_mem(srv, con, hctx, len);
474 z->next_out = (unsigned char *)hctx->output->ptr;
475 z->avail_out = hctx->output->size;
477 } while (z->avail_in != 0 || !done);
479 return 0;
482 static int stream_deflate_end(server *srv, handler_ctx *hctx) {
483 z_stream * const z = &(hctx->u.z);
484 int rc = deflateEnd(z);
485 if (Z_OK == rc || Z_DATA_ERROR == rc) return 0;
487 if (z->msg != NULL) {
488 log_error_write(srv, __FILE__, __LINE__, "sdss",
489 "deflateEnd error ret=", rc, ", msg=", z->msg);
490 } else {
491 log_error_write(srv, __FILE__, __LINE__, "sd",
492 "deflateEnd error ret=", rc);
494 return -1;
497 #endif
500 #ifdef USE_BZ2LIB
502 static int stream_bzip2_init(handler_ctx *hctx) {
503 bz_stream * const bz = &hctx->u.bz;
504 const plugin_data * const p = hctx->plugin_data;
505 bz->bzalloc = NULL;
506 bz->bzfree = NULL;
507 bz->opaque = NULL;
508 bz->total_in_lo32 = 0;
509 bz->total_in_hi32 = 0;
510 bz->total_out_lo32 = 0;
511 bz->total_out_hi32 = 0;
512 bz->next_out = hctx->output->ptr;
513 bz->avail_out = hctx->output->size;
515 if (BZ_OK != BZ2_bzCompressInit(bz,
516 p->conf.compression_level > 0
517 ? p->conf.compression_level
518 : 9, /* blocksize = 900k */
519 0, /* verbosity */
520 0)) { /* workFactor: default */
521 return -1;
524 return 0;
527 static int stream_bzip2_compress(server *srv, connection *con, handler_ctx *hctx, unsigned char *start, off_t st_size) {
528 bz_stream * const bz = &(hctx->u.bz);
529 size_t len;
531 bz->next_in = (char *)start;
532 bz->avail_in = st_size;
533 hctx->bytes_in += st_size;
535 /* compress data */
536 do {
537 if (BZ_RUN_OK != BZ2_bzCompress(bz, BZ_RUN)) return -1;
539 if (bz->avail_out == 0 || bz->avail_in > 0) {
540 len = hctx->output->size - bz->avail_out;
541 hctx->bytes_out += len;
542 stream_http_chunk_append_mem(srv, con, hctx, len);
543 bz->next_out = hctx->output->ptr;
544 bz->avail_out = hctx->output->size;
546 } while (bz->avail_in > 0);
548 return 0;
551 static int stream_bzip2_flush(server *srv, connection *con, handler_ctx *hctx, int end) {
552 bz_stream * const bz = &(hctx->u.bz);
553 const plugin_data *p = hctx->plugin_data;
554 size_t len;
555 int rc;
556 int done;
558 /* compress data */
559 do {
560 done = 1;
561 if (end) {
562 rc = BZ2_bzCompress(bz, BZ_FINISH);
563 if (rc == BZ_FINISH_OK) {
564 done = 0;
565 } else if (rc != BZ_STREAM_END) {
566 return -1;
568 } else if (bz->avail_in > 0) {
569 /* p->conf.sync_flush not implemented here,
570 * which would loop on BZ_FLUSH while BZ_FLUSH_OK
571 * until BZ_RUN_OK returned */
572 rc = BZ2_bzCompress(bz, BZ_RUN);
573 if (rc != BZ_RUN_OK) {
574 return -1;
578 len = hctx->output->size - bz->avail_out;
579 if (bz->avail_out == 0 || (len > 0 && (end || p->conf.sync_flush))) {
580 hctx->bytes_out += len;
581 stream_http_chunk_append_mem(srv, con, hctx, len);
582 bz->next_out = hctx->output->ptr;
583 bz->avail_out = hctx->output->size;
585 } while (bz->avail_in != 0 || !done);
587 return 0;
590 static int stream_bzip2_end(server *srv, handler_ctx *hctx) {
591 bz_stream * const bz = &(hctx->u.bz);
592 int rc = BZ2_bzCompressEnd(bz);
593 if (BZ_OK == rc || BZ_DATA_ERROR == rc) return 0;
595 log_error_write(srv, __FILE__, __LINE__, "sd",
596 "BZ2_bzCompressEnd error ret=", rc);
597 return -1;
600 #endif
603 static int mod_deflate_stream_init(handler_ctx *hctx) {
604 switch(hctx->compression_type) {
605 #ifdef USE_ZLIB
606 case HTTP_ACCEPT_ENCODING_GZIP:
607 case HTTP_ACCEPT_ENCODING_DEFLATE:
608 return stream_deflate_init(hctx);
609 #endif
610 #ifdef USE_BZ2LIB
611 case HTTP_ACCEPT_ENCODING_BZIP2:
612 return stream_bzip2_init(hctx);
613 #endif
614 default:
615 return -1;
619 static int mod_deflate_compress(server *srv, connection *con, handler_ctx *hctx, unsigned char *start, off_t st_size) {
620 if (0 == st_size) return 0;
621 switch(hctx->compression_type) {
622 #ifdef USE_ZLIB
623 case HTTP_ACCEPT_ENCODING_GZIP:
624 case HTTP_ACCEPT_ENCODING_DEFLATE:
625 return stream_deflate_compress(srv, con, hctx, start, st_size);
626 #endif
627 #ifdef USE_BZ2LIB
628 case HTTP_ACCEPT_ENCODING_BZIP2:
629 return stream_bzip2_compress(srv, con, hctx, start, st_size);
630 #endif
631 default:
632 UNUSED(srv);
633 UNUSED(con);
634 UNUSED(start);
635 return -1;
639 static int mod_deflate_stream_flush(server *srv, connection *con, handler_ctx *hctx, int end) {
640 if (0 == hctx->bytes_in) return 0;
641 switch(hctx->compression_type) {
642 #ifdef USE_ZLIB
643 case HTTP_ACCEPT_ENCODING_GZIP:
644 case HTTP_ACCEPT_ENCODING_DEFLATE:
645 return stream_deflate_flush(srv, con, hctx, end);
646 #endif
647 #ifdef USE_BZ2LIB
648 case HTTP_ACCEPT_ENCODING_BZIP2:
649 return stream_bzip2_flush(srv, con, hctx, end);
650 #endif
651 default:
652 UNUSED(srv);
653 UNUSED(con);
654 UNUSED(end);
655 return -1;
659 static void mod_deflate_note_ratio(server *srv, connection *con, handler_ctx *hctx) {
660 /* store compression ratio in con->environment
661 * for possible logging by mod_accesslog
662 * (late in response handling, so not seen by most other modules) */
663 /*(should be called only at end of successful response compression)*/
664 char ratio[LI_ITOSTRING_LENGTH];
665 if (0 == hctx->bytes_in) return;
666 li_itostrn(ratio, sizeof(ratio), hctx->bytes_out * 100 / hctx->bytes_in);
667 array_set_key_value(con->environment,
668 CONST_STR_LEN("ratio"),
669 ratio, strlen(ratio));
670 UNUSED(srv);
673 static int mod_deflate_stream_end(server *srv, handler_ctx *hctx) {
674 switch(hctx->compression_type) {
675 #ifdef USE_ZLIB
676 case HTTP_ACCEPT_ENCODING_GZIP:
677 case HTTP_ACCEPT_ENCODING_DEFLATE:
678 return stream_deflate_end(srv, hctx);
679 #endif
680 #ifdef USE_BZ2LIB
681 case HTTP_ACCEPT_ENCODING_BZIP2:
682 return stream_bzip2_end(srv, hctx);
683 #endif
684 default:
685 UNUSED(srv);
686 return -1;
690 static void deflate_compress_cleanup(server *srv, connection *con, handler_ctx *hctx) {
691 const plugin_data *p = hctx->plugin_data;
692 con->plugin_ctx[p->id] = NULL;
694 if (0 != mod_deflate_stream_end(srv, hctx)) {
695 log_error_write(srv, __FILE__, __LINE__, "s", "error closing stream");
698 #if 1 /* unnecessary if deflate.min-compress-size is set to a reasonable value */
699 if (hctx->bytes_in < hctx->bytes_out) {
700 log_error_write(srv, __FILE__, __LINE__, "sbsdsd",
701 "uri ", con->uri.path_raw, " in=", hctx->bytes_in, " smaller than out=", hctx->bytes_out);
703 #endif
705 handler_ctx_free(hctx);
709 static int mod_deflate_file_chunk(server *srv, connection *con, handler_ctx *hctx, chunk *c, off_t st_size) {
710 off_t abs_offset;
711 off_t toSend = -1;
712 char *start;
713 #ifdef USE_MMAP
714 off_t we_want_to_mmap = 2 MByte;
715 off_t we_want_to_send = st_size;
716 volatile int mapped = 0;/* quiet warning: might be clobbered by 'longjmp' */
717 #else
718 start = NULL;
719 #endif
721 if (-1 == c->file.fd) { /* open the file if not already open */
722 if (-1 == (c->file.fd = fdevent_open_cloexec(c->file.name->ptr, O_RDONLY, 0))) {
723 log_error_write(srv, __FILE__, __LINE__, "sbs", "open failed for:", c->file.name, strerror(errno));
725 return -1;
729 abs_offset = c->file.start + c->offset;
731 #ifdef USE_MMAP
732 /* mmap the buffer
733 * - first mmap
734 * - new mmap as the we are at the end of the last one */
735 if (c->file.mmap.start == MAP_FAILED ||
736 abs_offset == (off_t)(c->file.mmap.offset + c->file.mmap.length)) {
738 /* Optimizations for the future:
740 * adaptive mem-mapping
741 * the problem:
742 * we mmap() the whole file. If someone has alot large files and 32bit
743 * machine the virtual address area will be unrun and we will have a failing
744 * mmap() call.
745 * solution:
746 * only mmap 16M in one chunk and move the window as soon as we have finished
747 * the first 8M
749 * read-ahead buffering
750 * the problem:
751 * sending out several large files in parallel trashes the read-ahead of the
752 * kernel leading to long wait-for-seek times.
753 * solutions: (increasing complexity)
754 * 1. use madvise
755 * 2. use a internal read-ahead buffer in the chunk-structure
756 * 3. use non-blocking IO for file-transfers
757 * */
759 /* all mmap()ed areas are 512kb expect the last which might be smaller */
760 off_t to_mmap;
762 /* this is a remap, move the mmap-offset */
763 if (c->file.mmap.start != MAP_FAILED) {
764 munmap(c->file.mmap.start, c->file.mmap.length);
765 c->file.mmap.offset += we_want_to_mmap;
766 } else {
767 /* in case the range-offset is after the first mmap()ed area we skip the area */
768 c->file.mmap.offset = 0;
770 while (c->file.mmap.offset + we_want_to_mmap < c->file.start) {
771 c->file.mmap.offset += we_want_to_mmap;
775 /* length is rel, c->offset too, assume there is no limit at the mmap-boundaries */
776 to_mmap = (c->file.start + c->file.length) - c->file.mmap.offset;
777 if (to_mmap > we_want_to_mmap) to_mmap = we_want_to_mmap;
778 /* we have more to send than we can mmap() at once */
779 if (we_want_to_send > to_mmap) we_want_to_send = to_mmap;
781 if (MAP_FAILED == (c->file.mmap.start = mmap(0, (size_t)to_mmap, PROT_READ, MAP_SHARED, c->file.fd, c->file.mmap.offset))) {
782 /* close it here, otherwise we'd have to set FD_CLOEXEC */
784 log_error_write(srv, __FILE__, __LINE__, "ssbd", "mmap failed:",
785 strerror(errno), c->file.name, c->file.fd);
787 return -1;
790 c->file.mmap.length = to_mmap;
791 #ifdef HAVE_MADVISE
792 /* don't advise files < 64Kb */
793 if (c->file.mmap.length > (64 KByte) &&
794 0 != madvise(c->file.mmap.start, c->file.mmap.length, MADV_WILLNEED)) {
795 log_error_write(srv, __FILE__, __LINE__, "ssbd", "madvise failed:",
796 strerror(errno), c->file.name, c->file.fd);
798 #endif
800 /* chunk_reset() or chunk_free() will cleanup for us */
803 /* to_send = abs_mmap_end - abs_offset */
804 toSend = (c->file.mmap.offset + c->file.mmap.length) - abs_offset;
805 if (toSend > we_want_to_send) toSend = we_want_to_send;
807 if (toSend < 0) {
808 log_error_write(srv, __FILE__, __LINE__, "soooo",
809 "toSend is negative:",
810 toSend,
811 c->file.mmap.length,
812 abs_offset,
813 c->file.mmap.offset);
814 force_assert(toSend < 0);
817 start = c->file.mmap.start;
818 mapped = 1;
819 #endif
821 if (MAP_FAILED == c->file.mmap.start) {
822 toSend = st_size;
823 if (toSend > 2 MByte) toSend = 2 MByte;
824 if (NULL == (start = malloc((size_t)toSend)) || -1 == lseek(c->file.fd, abs_offset, SEEK_SET) || toSend != read(c->file.fd, start, (size_t)toSend)) {
825 log_error_write(srv, __FILE__, __LINE__, "sbss", "reading", c->file.name, "failed:", strerror(errno));
827 free(start);
828 return -1;
832 #ifdef USE_MMAP
833 if (mapped) {
834 signal(SIGBUS, sigbus_handler);
835 sigbus_jmp_valid = 1;
836 if (0 != sigsetjmp(sigbus_jmp, 1)) {
837 sigbus_jmp_valid = 0;
839 log_error_write(srv, __FILE__, __LINE__, "sbd", "SIGBUS in mmap:",
840 c->file.name, c->file.fd);
841 return -1;
844 #endif
846 if (mod_deflate_compress(srv, con, hctx,
847 (unsigned char *)start + (abs_offset - c->file.mmap.offset), toSend) < 0) {
848 log_error_write(srv, __FILE__, __LINE__, "s",
849 "compress failed.");
850 toSend = -1;
853 #ifdef USE_MMAP
854 if (mapped)
855 sigbus_jmp_valid = 0;
856 else
857 #endif
858 free(start);
860 return toSend;
864 static handler_t deflate_compress_response(server *srv, connection *con, handler_ctx *hctx) {
865 off_t len, max;
866 int close_stream;
868 /* move all chunk from write_queue into our in_queue, then adjust
869 * counters since con->write_queue is reused for compressed output */
870 len = chunkqueue_length(con->write_queue);
871 chunkqueue_remove_finished_chunks(con->write_queue);
872 chunkqueue_append_chunkqueue(hctx->in_queue, con->write_queue);
873 con->write_queue->bytes_in -= len;
874 con->write_queue->bytes_out -= len;
876 max = chunkqueue_length(hctx->in_queue);
877 #if 0
878 /* calculate max bytes to compress for this call */
879 if (p->conf.sync_flush && max > (len = p->conf.work_block_size << 10)) {
880 max = len;
882 #endif
884 /* Compress chunks from in_queue into chunks for write_queue */
885 while (max) {
886 chunk *c = hctx->in_queue->first;
888 switch(c->type) {
889 case MEM_CHUNK:
890 len = buffer_string_length(c->mem) - c->offset;
891 if (len > max) len = max;
892 if (mod_deflate_compress(srv, con, hctx, (unsigned char *)c->mem->ptr+c->offset, len) < 0) {
893 log_error_write(srv, __FILE__, __LINE__, "s",
894 "compress failed.");
895 return HANDLER_ERROR;
897 break;
898 case FILE_CHUNK:
899 len = c->file.length - c->offset;
900 if (len > max) len = max;
901 if ((len = mod_deflate_file_chunk(srv, con, hctx, c, len)) < 0) {
902 log_error_write(srv, __FILE__, __LINE__, "s",
903 "compress file chunk failed.");
904 return HANDLER_ERROR;
906 break;
907 default:
908 log_error_write(srv, __FILE__, __LINE__, "ds", c, "type not known");
909 return HANDLER_ERROR;
912 max -= len;
913 chunkqueue_mark_written(hctx->in_queue, len);
916 /*(currently should always be true)*/
917 /*(current implementation requires response be complete)*/
918 close_stream = (con->file_finished && chunkqueue_is_empty(hctx->in_queue));
919 if (mod_deflate_stream_flush(srv, con, hctx, close_stream) < 0) {
920 log_error_write(srv, __FILE__, __LINE__, "s", "flush error");
921 return HANDLER_ERROR;
924 return close_stream ? HANDLER_FINISHED : HANDLER_GO_ON;
928 #define PATCH(x) \
929 p->conf.x = s->x;
930 static int mod_deflate_patch_connection(server *srv, connection *con, plugin_data *p) {
931 size_t i, j;
932 plugin_config *s = p->config_storage[0];
934 PATCH(mimetypes);
935 PATCH(allowed_encodings);
936 PATCH(max_compress_size);
937 PATCH(min_compress_size);
938 PATCH(compression_level);
939 PATCH(output_buffer_size);
940 PATCH(work_block_size);
941 PATCH(max_loadavg);
943 /* skip the first, the global context */
944 for (i = 1; i < srv->config_context->used; i++) {
945 data_config *dc = (data_config *)srv->config_context->data[i];
946 s = p->config_storage[i];
948 /* condition didn't match */
949 if (!config_check_cond(srv, con, dc)) continue;
951 /* merge config */
952 for (j = 0; j < dc->value->used; j++) {
953 data_unset *du = dc->value->data[j];
955 if (buffer_is_equal_string(du->key, CONST_STR_LEN("deflate.mimetypes"))) {
956 PATCH(mimetypes);
957 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("deflate.allowed-encodings"))) {
958 PATCH(allowed_encodings);
959 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("deflate.max-compress-size"))) {
960 PATCH(max_compress_size);
961 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("deflate.min-compress-size"))) {
962 PATCH(min_compress_size);
963 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("deflate.compression-level"))) {
964 PATCH(compression_level);
965 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("deflate.output-buffer-size"))) {
966 PATCH(output_buffer_size);
967 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("deflate.work-block-size"))) {
968 PATCH(work_block_size);
969 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("deflate.max-loadavg"))) {
970 PATCH(max_loadavg);
975 return 0;
977 #undef PATCH
979 static int mod_deflate_choose_encoding (const char *value, plugin_data *p, const char **label) {
980 /* get client side support encodings */
981 int accept_encoding = 0;
982 #if !defined(USE_ZLIB) && !defined(USE_BZ2LIB)
983 UNUSED(value);
984 #endif
985 #ifdef USE_ZLIB
986 if (NULL != strstr(value, "gzip")) accept_encoding |= HTTP_ACCEPT_ENCODING_GZIP;
987 else if (NULL != strstr(value, "x-gzip")) accept_encoding |= HTTP_ACCEPT_ENCODING_X_GZIP;
988 if (NULL != strstr(value, "deflate")) accept_encoding |= HTTP_ACCEPT_ENCODING_DEFLATE;
989 #endif
990 /* if (NULL != strstr(value, "compress")) accept_encoding |= HTTP_ACCEPT_ENCODING_COMPRESS; */
991 #ifdef USE_BZ2LIB
992 if (p->conf.allowed_encodings & (HTTP_ACCEPT_ENCODING_BZIP2 | HTTP_ACCEPT_ENCODING_X_BZIP2)) {
993 if (NULL != strstr(value, "bzip2")) accept_encoding |= HTTP_ACCEPT_ENCODING_BZIP2;
994 else if (NULL != strstr(value, "x-bzip2")) accept_encoding |= HTTP_ACCEPT_ENCODING_X_BZIP2;
996 #endif
997 /* if (NULL != strstr(value, "identity")) accept_encoding |= HTTP_ACCEPT_ENCODING_IDENTITY; */
999 /* mask to limit to allowed_encodings */
1000 accept_encoding &= p->conf.allowed_encodings;
1002 /* select best matching encoding */
1003 #ifdef USE_BZ2LIB
1004 if (accept_encoding & HTTP_ACCEPT_ENCODING_BZIP2) {
1005 *label = "bzip2";
1006 return HTTP_ACCEPT_ENCODING_BZIP2;
1007 } else if (accept_encoding & HTTP_ACCEPT_ENCODING_X_BZIP2) {
1008 *label = "x-bzip2";
1009 return HTTP_ACCEPT_ENCODING_BZIP2;
1010 } else
1011 #endif
1012 if (accept_encoding & HTTP_ACCEPT_ENCODING_GZIP) {
1013 *label = "gzip";
1014 return HTTP_ACCEPT_ENCODING_GZIP;
1015 } else if (accept_encoding & HTTP_ACCEPT_ENCODING_X_GZIP) {
1016 *label = "x-gzip";
1017 return HTTP_ACCEPT_ENCODING_GZIP;
1018 } else if (accept_encoding & HTTP_ACCEPT_ENCODING_DEFLATE) {
1019 *label = "deflate";
1020 return HTTP_ACCEPT_ENCODING_DEFLATE;
1021 } else {
1022 return 0;
1026 CONNECTION_FUNC(mod_deflate_handle_response_start) {
1027 plugin_data *p = p_d;
1028 data_string *ds;
1029 handler_ctx *hctx;
1030 const char *label;
1031 off_t len;
1032 size_t etaglen = 0;
1033 int compression_type;
1034 handler_t rc;
1036 /*(current implementation requires response be complete)*/
1037 if (!con->file_finished) return HANDLER_GO_ON;
1038 if (con->request.http_method == HTTP_METHOD_HEAD) return HANDLER_GO_ON;
1039 if (con->parsed_response & HTTP_TRANSFER_ENCODING_CHUNKED) return HANDLER_GO_ON;
1041 /* disable compression for some http status types. */
1042 switch(con->http_status) {
1043 case 100:
1044 case 101:
1045 case 204:
1046 case 205:
1047 case 304:
1048 /* disable compression as we have no response entity */
1049 return HANDLER_GO_ON;
1050 default:
1051 break;
1054 mod_deflate_patch_connection(srv, con, p);
1056 /* check if deflate configured for any mimetypes */
1057 if (!p->conf.mimetypes->used) return HANDLER_GO_ON;
1059 /* check if size of response is below min-compress-size or exceeds max*/
1060 /* (con->file_finished checked at top of routine) */
1061 len = chunkqueue_length(con->write_queue);
1062 if (len <= (off_t)p->conf.min_compress_size) return HANDLER_GO_ON;
1063 if (p->conf.max_compress_size /*(max_compress_size in KB)*/
1064 && len > ((off_t)p->conf.max_compress_size << 10)) {
1065 return HANDLER_GO_ON;
1068 /* Check if response has a Content-Encoding. */
1069 ds = (data_string *)array_get_element(con->response.headers, "Content-Encoding");
1070 if (NULL != ds) return HANDLER_GO_ON;
1072 /* Check Accept-Encoding for supported encoding. */
1073 ds = (data_string *)array_get_element(con->request.headers, "Accept-Encoding");
1074 if (NULL == ds) return HANDLER_GO_ON;
1076 /* find matching encodings */
1077 compression_type = mod_deflate_choose_encoding(ds->value->ptr, p, &label);
1078 if (!compression_type) return HANDLER_GO_ON;
1080 /* Check mimetype in response header "Content-Type" */
1081 if (NULL != (ds = (data_string *)array_get_element(con->response.headers, "Content-Type"))) {
1082 int found = 0;
1083 size_t m;
1084 for (m = 0; m < p->conf.mimetypes->used; ++m) {
1085 data_string *mimetype = (data_string *)p->conf.mimetypes->data[m];
1086 if (0 == strncmp(mimetype->value->ptr, ds->value->ptr, buffer_string_length(mimetype->value))) {
1087 /* mimetype found */
1088 found = 1;
1089 break;
1092 if (!found) return HANDLER_GO_ON;
1094 #if 0
1095 if (0 == strncasecmp(ds->value->ptr, "application/x-javascript", 24)) {
1096 /*reset compress type to deflate for javascript
1097 * prevent buggy IE6 SP1 doesn't work for js in IFrame
1099 compression_type = HTTP_ACCEPT_ENCODING_DEFLATE;
1101 #endif
1102 } else {
1103 /* If no Content-Type set, compress only if first p->conf.mimetypes value is "" */
1104 data_string *mimetype = (data_string *)p->conf.mimetypes->data[0];
1105 if (!buffer_string_is_empty(mimetype->value)) return HANDLER_GO_ON;
1108 /* Vary: Accept-Encoding (response might change according to request Accept-Encoding) */
1109 if (NULL != (ds = (data_string *)array_get_element(con->response.headers, "Vary"))) {
1110 if (NULL == strstr(ds->value->ptr, "Accept-Encoding")) {
1111 buffer_append_string_len(ds->value, CONST_STR_LEN(",Accept-Encoding"));
1113 } else {
1114 response_header_insert(srv, con, CONST_STR_LEN("Vary"),
1115 CONST_STR_LEN("Accept-Encoding"));
1118 /* check ETag as is done in http_response_handle_cachable()
1119 * (slightly imperfect (close enough?) match of ETag "000000" to "000000-gzip") */
1120 ds = (data_string *)array_get_element(con->response.headers, "ETag");
1121 if (NULL != ds) {
1122 etaglen = buffer_string_length(ds->value);
1123 if (etaglen
1124 && con->http_status < 300 /*(want 2xx only)*/
1125 && con->request.http_if_none_match
1126 && 0 == strncmp(con->request.http_if_none_match, ds->value->ptr, etaglen-1)
1127 && con->request.http_if_none_match[etaglen-1] == '-'
1128 && 0 == strncmp(con->request.http_if_none_match+etaglen, label, strlen(label))) {
1130 if ( HTTP_METHOD_GET == con->request.http_method
1131 || HTTP_METHOD_HEAD == con->request.http_method) {
1132 /* modify ETag response header in-place to remove '"' and append '-label"' */
1133 ds->value->ptr[etaglen-1] = '-'; /*(overwrite end '"')*/
1134 buffer_append_string(ds->value, label);
1135 buffer_append_string_len(ds->value, CONST_STR_LEN("\""));
1136 /*buffer_copy_buffer(con->physical.etag, ds->value);*//*(keep in sync?)*/
1137 con->http_status = 304;
1138 } else {
1139 con->http_status = 412;
1142 /* response_start hook occurs after error docs have been handled.
1143 * For now, send back empty response body.
1144 * In the future, might extract the error doc code so that it
1145 * might be run again if response_start hooks return with
1146 * changed http_status and con->mode = DIRECT */
1147 con->response.transfer_encoding &= ~HTTP_TRANSFER_ENCODING_CHUNKED;
1148 con->parsed_response &= ~HTTP_CONTENT_LENGTH;
1149 chunkqueue_reset(con->write_queue);
1150 con->file_finished = 1;
1152 con->mode = DIRECT;
1153 return HANDLER_GO_ON;
1157 if (0.0 < p->conf.max_loadavg && p->conf.max_loadavg < srv->srvconf.loadavg[0]) {
1158 return HANDLER_GO_ON;
1161 /* update ETag, if ETag response header is set */
1162 if (etaglen) {
1163 /* modify ETag response header in-place to remove '"' and append '-label"' */
1164 ds->value->ptr[etaglen-1] = '-'; /*(overwrite end '"')*/
1165 buffer_append_string(ds->value, label);
1166 buffer_append_string_len(ds->value, CONST_STR_LEN("\""));
1167 /*buffer_copy_buffer(con->physical.etag, ds->value);*//*(keep in sync?)*/
1170 /* set Content-Encoding to show selected compression type */
1171 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Encoding"), label, strlen(label));
1173 /* clear Content-Length and con->write_queue if HTTP HEAD request
1174 * (alternatively, could return original Content-Length with HEAD
1175 * request if ETag not modified and Content-Encoding not added) */
1176 if (HTTP_METHOD_HEAD == con->request.http_method) {
1177 /* ensure that uncompressed Content-Length is not sent in HEAD response */
1178 chunkqueue_reset(con->write_queue);
1179 if (con->parsed_response & HTTP_CONTENT_LENGTH) {
1180 con->parsed_response &= ~HTTP_CONTENT_LENGTH;
1181 if (NULL != (ds = (data_string*) array_get_element(con->response.headers, "Content-Length"))) {
1182 buffer_reset(ds->value); /* headers with empty values are ignored for output */
1185 return HANDLER_GO_ON;
1188 /* future: might use ETag to check if compressed content is in compressed file cache */
1189 /*if (etaglen) { ... } *//* return if in file cache after updating con->write_queue */
1191 /* enable compression */
1192 p->conf.sync_flush =
1193 (con->conf.stream_response_body && 0 == p->conf.output_buffer_size);
1194 hctx = handler_ctx_init();
1195 hctx->plugin_data = p;
1196 hctx->compression_type = compression_type;
1197 /* setup output buffer */
1198 buffer_string_set_length(p->tmp_buf, 0);
1199 hctx->output = p->tmp_buf;
1200 if (0 != mod_deflate_stream_init(hctx)) {
1201 /*(should not happen unless ENOMEM)*/
1202 handler_ctx_free(hctx);
1203 log_error_write(srv, __FILE__, __LINE__, "ss",
1204 "Failed to initialize compression", label);
1205 /* restore prior Etag and unset Content-Encoding */
1206 if (etaglen) {
1207 ds->value->ptr[etaglen-1] = '"'; /*(overwrite '-')*/
1208 buffer_string_set_length(ds->value, etaglen);
1210 ds = (data_string *)array_get_element(con->response.headers, "Content-Encoding");
1211 if (ds) buffer_reset(ds->value); /* headers with empty values are ignored for output */
1212 return HANDLER_GO_ON;
1215 con->parsed_response &= ~HTTP_CONTENT_LENGTH;
1216 con->plugin_ctx[p->id] = hctx;
1218 rc = deflate_compress_response(srv, con, hctx);
1219 if (HANDLER_GO_ON != rc) {
1220 if (HANDLER_FINISHED == rc) {
1221 mod_deflate_note_ratio(srv, con, hctx);
1223 deflate_compress_cleanup(srv, con, hctx);
1224 if (HANDLER_ERROR == rc) return HANDLER_ERROR;
1227 return HANDLER_GO_ON;
1230 static handler_t mod_deflate_cleanup(server *srv, connection *con, void *p_d) {
1231 plugin_data *p = p_d;
1232 handler_ctx *hctx = con->plugin_ctx[p->id];
1234 if (NULL != hctx) deflate_compress_cleanup(srv, con, hctx);
1236 return HANDLER_GO_ON;
1239 int mod_deflate_plugin_init(plugin *p);
1240 int mod_deflate_plugin_init(plugin *p) {
1241 p->version = LIGHTTPD_VERSION_ID;
1242 p->name = buffer_init_string("deflate");
1244 p->init = mod_deflate_init;
1245 p->cleanup = mod_deflate_free;
1246 p->set_defaults = mod_deflate_setdefaults;
1247 p->connection_reset = mod_deflate_cleanup;
1248 p->handle_connection_close = mod_deflate_cleanup;
1249 p->handle_response_start = mod_deflate_handle_response_start;
1251 p->data = NULL;
1253 return 0;