Annotation of embedaddon/nginx/src/http/modules/ngx_http_gzip_filter_module.c, revision 1.1
1.1 ! misho 1:
! 2: /*
! 3: * Copyright (C) Igor Sysoev
! 4: * Copyright (C) Nginx, Inc.
! 5: */
! 6:
! 7:
! 8: #include <ngx_config.h>
! 9: #include <ngx_core.h>
! 10: #include <ngx_http.h>
! 11:
! 12: #include <zlib.h>
! 13:
! 14:
! 15: typedef struct {
! 16: ngx_flag_t enable;
! 17: ngx_flag_t no_buffer;
! 18:
! 19: ngx_hash_t types;
! 20:
! 21: ngx_bufs_t bufs;
! 22:
! 23: size_t postpone_gzipping;
! 24: ngx_int_t level;
! 25: size_t wbits;
! 26: size_t memlevel;
! 27: ssize_t min_length;
! 28:
! 29: ngx_array_t *types_keys;
! 30: } ngx_http_gzip_conf_t;
! 31:
! 32:
! 33: typedef struct {
! 34: ngx_chain_t *in;
! 35: ngx_chain_t *free;
! 36: ngx_chain_t *busy;
! 37: ngx_chain_t *out;
! 38: ngx_chain_t **last_out;
! 39:
! 40: ngx_chain_t *copied;
! 41: ngx_chain_t *copy_buf;
! 42:
! 43: ngx_buf_t *in_buf;
! 44: ngx_buf_t *out_buf;
! 45: ngx_int_t bufs;
! 46:
! 47: void *preallocated;
! 48: char *free_mem;
! 49: ngx_uint_t allocated;
! 50:
! 51: int wbits;
! 52: int memlevel;
! 53:
! 54: unsigned flush:4;
! 55: unsigned redo:1;
! 56: unsigned done:1;
! 57: unsigned nomem:1;
! 58: unsigned gzheader:1;
! 59: unsigned buffering:1;
! 60:
! 61: size_t zin;
! 62: size_t zout;
! 63:
! 64: uint32_t crc32;
! 65: z_stream zstream;
! 66: ngx_http_request_t *request;
! 67: } ngx_http_gzip_ctx_t;
! 68:
! 69:
! 70: #if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED)
! 71:
! 72: struct gztrailer {
! 73: uint32_t crc32;
! 74: uint32_t zlen;
! 75: };
! 76:
! 77: #else /* NGX_HAVE_BIG_ENDIAN || !NGX_HAVE_NONALIGNED */
! 78:
! 79: struct gztrailer {
! 80: u_char crc32[4];
! 81: u_char zlen[4];
! 82: };
! 83:
! 84: #endif
! 85:
! 86:
! 87: static void ngx_http_gzip_filter_memory(ngx_http_request_t *r,
! 88: ngx_http_gzip_ctx_t *ctx);
! 89: static ngx_int_t ngx_http_gzip_filter_buffer(ngx_http_gzip_ctx_t *ctx,
! 90: ngx_chain_t *in);
! 91: static ngx_int_t ngx_http_gzip_filter_deflate_start(ngx_http_request_t *r,
! 92: ngx_http_gzip_ctx_t *ctx);
! 93: static ngx_int_t ngx_http_gzip_filter_gzheader(ngx_http_request_t *r,
! 94: ngx_http_gzip_ctx_t *ctx);
! 95: static ngx_int_t ngx_http_gzip_filter_add_data(ngx_http_request_t *r,
! 96: ngx_http_gzip_ctx_t *ctx);
! 97: static ngx_int_t ngx_http_gzip_filter_get_buf(ngx_http_request_t *r,
! 98: ngx_http_gzip_ctx_t *ctx);
! 99: static ngx_int_t ngx_http_gzip_filter_deflate(ngx_http_request_t *r,
! 100: ngx_http_gzip_ctx_t *ctx);
! 101: static ngx_int_t ngx_http_gzip_filter_deflate_end(ngx_http_request_t *r,
! 102: ngx_http_gzip_ctx_t *ctx);
! 103:
! 104: static void *ngx_http_gzip_filter_alloc(void *opaque, u_int items,
! 105: u_int size);
! 106: static void ngx_http_gzip_filter_free(void *opaque, void *address);
! 107: static void ngx_http_gzip_filter_free_copy_buf(ngx_http_request_t *r,
! 108: ngx_http_gzip_ctx_t *ctx);
! 109:
! 110: static ngx_int_t ngx_http_gzip_add_variables(ngx_conf_t *cf);
! 111: static ngx_int_t ngx_http_gzip_ratio_variable(ngx_http_request_t *r,
! 112: ngx_http_variable_value_t *v, uintptr_t data);
! 113:
! 114: static ngx_int_t ngx_http_gzip_filter_init(ngx_conf_t *cf);
! 115: static void *ngx_http_gzip_create_conf(ngx_conf_t *cf);
! 116: static char *ngx_http_gzip_merge_conf(ngx_conf_t *cf,
! 117: void *parent, void *child);
! 118: static char *ngx_http_gzip_window(ngx_conf_t *cf, void *post, void *data);
! 119: static char *ngx_http_gzip_hash(ngx_conf_t *cf, void *post, void *data);
! 120:
! 121:
! 122: static ngx_conf_num_bounds_t ngx_http_gzip_comp_level_bounds = {
! 123: ngx_conf_check_num_bounds, 1, 9
! 124: };
! 125:
! 126: static ngx_conf_post_handler_pt ngx_http_gzip_window_p = ngx_http_gzip_window;
! 127: static ngx_conf_post_handler_pt ngx_http_gzip_hash_p = ngx_http_gzip_hash;
! 128:
! 129:
! 130: static ngx_command_t ngx_http_gzip_filter_commands[] = {
! 131:
! 132: { ngx_string("gzip"),
! 133: NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
! 134: |NGX_CONF_FLAG,
! 135: ngx_conf_set_flag_slot,
! 136: NGX_HTTP_LOC_CONF_OFFSET,
! 137: offsetof(ngx_http_gzip_conf_t, enable),
! 138: NULL },
! 139:
! 140: { ngx_string("gzip_buffers"),
! 141: NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
! 142: ngx_conf_set_bufs_slot,
! 143: NGX_HTTP_LOC_CONF_OFFSET,
! 144: offsetof(ngx_http_gzip_conf_t, bufs),
! 145: NULL },
! 146:
! 147: { ngx_string("gzip_types"),
! 148: NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
! 149: ngx_http_types_slot,
! 150: NGX_HTTP_LOC_CONF_OFFSET,
! 151: offsetof(ngx_http_gzip_conf_t, types_keys),
! 152: &ngx_http_html_default_types[0] },
! 153:
! 154: { ngx_string("gzip_comp_level"),
! 155: NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
! 156: ngx_conf_set_num_slot,
! 157: NGX_HTTP_LOC_CONF_OFFSET,
! 158: offsetof(ngx_http_gzip_conf_t, level),
! 159: &ngx_http_gzip_comp_level_bounds },
! 160:
! 161: { ngx_string("gzip_window"),
! 162: NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
! 163: ngx_conf_set_size_slot,
! 164: NGX_HTTP_LOC_CONF_OFFSET,
! 165: offsetof(ngx_http_gzip_conf_t, wbits),
! 166: &ngx_http_gzip_window_p },
! 167:
! 168: { ngx_string("gzip_hash"),
! 169: NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
! 170: ngx_conf_set_size_slot,
! 171: NGX_HTTP_LOC_CONF_OFFSET,
! 172: offsetof(ngx_http_gzip_conf_t, memlevel),
! 173: &ngx_http_gzip_hash_p },
! 174:
! 175: { ngx_string("postpone_gzipping"),
! 176: NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
! 177: ngx_conf_set_size_slot,
! 178: NGX_HTTP_LOC_CONF_OFFSET,
! 179: offsetof(ngx_http_gzip_conf_t, postpone_gzipping),
! 180: NULL },
! 181:
! 182: { ngx_string("gzip_no_buffer"),
! 183: NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
! 184: ngx_conf_set_flag_slot,
! 185: NGX_HTTP_LOC_CONF_OFFSET,
! 186: offsetof(ngx_http_gzip_conf_t, no_buffer),
! 187: NULL },
! 188:
! 189: { ngx_string("gzip_min_length"),
! 190: NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
! 191: ngx_conf_set_size_slot,
! 192: NGX_HTTP_LOC_CONF_OFFSET,
! 193: offsetof(ngx_http_gzip_conf_t, min_length),
! 194: NULL },
! 195:
! 196: ngx_null_command
! 197: };
! 198:
! 199:
! 200: static ngx_http_module_t ngx_http_gzip_filter_module_ctx = {
! 201: ngx_http_gzip_add_variables, /* preconfiguration */
! 202: ngx_http_gzip_filter_init, /* postconfiguration */
! 203:
! 204: NULL, /* create main configuration */
! 205: NULL, /* init main configuration */
! 206:
! 207: NULL, /* create server configuration */
! 208: NULL, /* merge server configuration */
! 209:
! 210: ngx_http_gzip_create_conf, /* create location configuration */
! 211: ngx_http_gzip_merge_conf /* merge location configuration */
! 212: };
! 213:
! 214:
! 215: ngx_module_t ngx_http_gzip_filter_module = {
! 216: NGX_MODULE_V1,
! 217: &ngx_http_gzip_filter_module_ctx, /* module context */
! 218: ngx_http_gzip_filter_commands, /* module directives */
! 219: NGX_HTTP_MODULE, /* module type */
! 220: NULL, /* init master */
! 221: NULL, /* init module */
! 222: NULL, /* init process */
! 223: NULL, /* init thread */
! 224: NULL, /* exit thread */
! 225: NULL, /* exit process */
! 226: NULL, /* exit master */
! 227: NGX_MODULE_V1_PADDING
! 228: };
! 229:
! 230:
! 231: static ngx_str_t ngx_http_gzip_ratio = ngx_string("gzip_ratio");
! 232:
! 233: static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
! 234: static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
! 235:
! 236:
! 237: static ngx_int_t
! 238: ngx_http_gzip_header_filter(ngx_http_request_t *r)
! 239: {
! 240: ngx_table_elt_t *h;
! 241: ngx_http_gzip_ctx_t *ctx;
! 242: ngx_http_gzip_conf_t *conf;
! 243:
! 244: conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
! 245:
! 246: if (!conf->enable
! 247: || (r->headers_out.status != NGX_HTTP_OK
! 248: && r->headers_out.status != NGX_HTTP_FORBIDDEN
! 249: && r->headers_out.status != NGX_HTTP_NOT_FOUND)
! 250: || (r->headers_out.content_encoding
! 251: && r->headers_out.content_encoding->value.len)
! 252: || (r->headers_out.content_length_n != -1
! 253: && r->headers_out.content_length_n < conf->min_length)
! 254: || ngx_http_test_content_type(r, &conf->types) == NULL
! 255: || r->header_only)
! 256: {
! 257: return ngx_http_next_header_filter(r);
! 258: }
! 259:
! 260: r->gzip_vary = 1;
! 261:
! 262: #if (NGX_HTTP_DEGRADATION)
! 263: {
! 264: ngx_http_core_loc_conf_t *clcf;
! 265:
! 266: clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
! 267:
! 268: if (clcf->gzip_disable_degradation && ngx_http_degraded(r)) {
! 269: return ngx_http_next_header_filter(r);
! 270: }
! 271: }
! 272: #endif
! 273:
! 274: if (!r->gzip_tested) {
! 275: if (ngx_http_gzip_ok(r) != NGX_OK) {
! 276: return ngx_http_next_header_filter(r);
! 277: }
! 278:
! 279: } else if (!r->gzip_ok) {
! 280: return ngx_http_next_header_filter(r);
! 281: }
! 282:
! 283: ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_gzip_ctx_t));
! 284: if (ctx == NULL) {
! 285: return NGX_ERROR;
! 286: }
! 287:
! 288: ngx_http_set_ctx(r, ctx, ngx_http_gzip_filter_module);
! 289:
! 290: ctx->request = r;
! 291: ctx->buffering = (conf->postpone_gzipping != 0);
! 292:
! 293: ngx_http_gzip_filter_memory(r, ctx);
! 294:
! 295: h = ngx_list_push(&r->headers_out.headers);
! 296: if (h == NULL) {
! 297: return NGX_ERROR;
! 298: }
! 299:
! 300: h->hash = 1;
! 301: ngx_str_set(&h->key, "Content-Encoding");
! 302: ngx_str_set(&h->value, "gzip");
! 303: r->headers_out.content_encoding = h;
! 304:
! 305: r->main_filter_need_in_memory = 1;
! 306:
! 307: ngx_http_clear_content_length(r);
! 308: ngx_http_clear_accept_ranges(r);
! 309: ngx_http_clear_etag(r);
! 310:
! 311: return ngx_http_next_header_filter(r);
! 312: }
! 313:
! 314:
! 315: static ngx_int_t
! 316: ngx_http_gzip_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
! 317: {
! 318: int rc;
! 319: ngx_chain_t *cl;
! 320: ngx_http_gzip_ctx_t *ctx;
! 321:
! 322: ctx = ngx_http_get_module_ctx(r, ngx_http_gzip_filter_module);
! 323:
! 324: if (ctx == NULL || ctx->done || r->header_only) {
! 325: return ngx_http_next_body_filter(r, in);
! 326: }
! 327:
! 328: ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
! 329: "http gzip filter");
! 330:
! 331: if (ctx->buffering) {
! 332:
! 333: /*
! 334: * With default memory settings zlib starts to output gzipped data
! 335: * only after it has got about 90K, so it makes sense to allocate
! 336: * zlib memory (200-400K) only after we have enough data to compress.
! 337: * Although we copy buffers, nevertheless for not big responses
! 338: * this allows to allocate zlib memory, to compress and to output
! 339: * the response in one step using hot CPU cache.
! 340: */
! 341:
! 342: if (in) {
! 343: switch (ngx_http_gzip_filter_buffer(ctx, in)) {
! 344:
! 345: case NGX_OK:
! 346: return NGX_OK;
! 347:
! 348: case NGX_DONE:
! 349: in = NULL;
! 350: break;
! 351:
! 352: default: /* NGX_ERROR */
! 353: goto failed;
! 354: }
! 355:
! 356: } else {
! 357: ctx->buffering = 0;
! 358: }
! 359: }
! 360:
! 361: if (ctx->preallocated == NULL) {
! 362: if (ngx_http_gzip_filter_deflate_start(r, ctx) != NGX_OK) {
! 363: goto failed;
! 364: }
! 365: }
! 366:
! 367: if (in) {
! 368: if (ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) {
! 369: goto failed;
! 370: }
! 371: }
! 372:
! 373: if (ctx->nomem) {
! 374:
! 375: /* flush busy buffers */
! 376:
! 377: if (ngx_http_next_body_filter(r, NULL) == NGX_ERROR) {
! 378: goto failed;
! 379: }
! 380:
! 381: cl = NULL;
! 382:
! 383: ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &cl,
! 384: (ngx_buf_tag_t) &ngx_http_gzip_filter_module);
! 385: ctx->nomem = 0;
! 386: }
! 387:
! 388: for ( ;; ) {
! 389:
! 390: /* cycle while we can write to a client */
! 391:
! 392: for ( ;; ) {
! 393:
! 394: /* cycle while there is data to feed zlib and ... */
! 395:
! 396: rc = ngx_http_gzip_filter_add_data(r, ctx);
! 397:
! 398: if (rc == NGX_DECLINED) {
! 399: break;
! 400: }
! 401:
! 402: if (rc == NGX_AGAIN) {
! 403: continue;
! 404: }
! 405:
! 406:
! 407: /* ... there are buffers to write zlib output */
! 408:
! 409: rc = ngx_http_gzip_filter_get_buf(r, ctx);
! 410:
! 411: if (rc == NGX_DECLINED) {
! 412: break;
! 413: }
! 414:
! 415: if (rc == NGX_ERROR) {
! 416: goto failed;
! 417: }
! 418:
! 419:
! 420: rc = ngx_http_gzip_filter_deflate(r, ctx);
! 421:
! 422: if (rc == NGX_OK) {
! 423: break;
! 424: }
! 425:
! 426: if (rc == NGX_ERROR) {
! 427: goto failed;
! 428: }
! 429:
! 430: /* rc == NGX_AGAIN */
! 431: }
! 432:
! 433: if (ctx->out == NULL) {
! 434: ngx_http_gzip_filter_free_copy_buf(r, ctx);
! 435:
! 436: return ctx->busy ? NGX_AGAIN : NGX_OK;
! 437: }
! 438:
! 439: if (!ctx->gzheader) {
! 440: if (ngx_http_gzip_filter_gzheader(r, ctx) != NGX_OK) {
! 441: goto failed;
! 442: }
! 443: }
! 444:
! 445: rc = ngx_http_next_body_filter(r, ctx->out);
! 446:
! 447: if (rc == NGX_ERROR) {
! 448: goto failed;
! 449: }
! 450:
! 451: ngx_http_gzip_filter_free_copy_buf(r, ctx);
! 452:
! 453: ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &ctx->out,
! 454: (ngx_buf_tag_t) &ngx_http_gzip_filter_module);
! 455: ctx->last_out = &ctx->out;
! 456:
! 457: ctx->nomem = 0;
! 458:
! 459: if (ctx->done) {
! 460: return rc;
! 461: }
! 462: }
! 463:
! 464: /* unreachable */
! 465:
! 466: failed:
! 467:
! 468: ctx->done = 1;
! 469:
! 470: if (ctx->preallocated) {
! 471: deflateEnd(&ctx->zstream);
! 472:
! 473: ngx_pfree(r->pool, ctx->preallocated);
! 474: }
! 475:
! 476: ngx_http_gzip_filter_free_copy_buf(r, ctx);
! 477:
! 478: return NGX_ERROR;
! 479: }
! 480:
! 481:
! 482: static void
! 483: ngx_http_gzip_filter_memory(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
! 484: {
! 485: int wbits, memlevel;
! 486: ngx_http_gzip_conf_t *conf;
! 487:
! 488: conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
! 489:
! 490: wbits = conf->wbits;
! 491: memlevel = conf->memlevel;
! 492:
! 493: if (r->headers_out.content_length_n > 0) {
! 494:
! 495: /* the actual zlib window size is smaller by 262 bytes */
! 496:
! 497: while (r->headers_out.content_length_n < ((1 << (wbits - 1)) - 262)) {
! 498: wbits--;
! 499: memlevel--;
! 500: }
! 501:
! 502: if (memlevel < 1) {
! 503: memlevel = 1;
! 504: }
! 505: }
! 506:
! 507: ctx->wbits = wbits;
! 508: ctx->memlevel = memlevel;
! 509:
! 510: /*
! 511: * We preallocate a memory for zlib in one buffer (200K-400K), this
! 512: * decreases a number of malloc() and free() calls and also probably
! 513: * decreases a number of syscalls (sbrk()/mmap() and so on).
! 514: * Besides we free the memory as soon as a gzipping will complete
! 515: * and do not wait while a whole response will be sent to a client.
! 516: *
! 517: * 8K is for zlib deflate_state, it takes
! 518: * *) 5816 bytes on i386 and sparc64 (32-bit mode)
! 519: * *) 5920 bytes on amd64 and sparc64
! 520: */
! 521:
! 522: ctx->allocated = 8192 + (1 << (wbits + 2)) + (1 << (memlevel + 9));
! 523: }
! 524:
! 525:
! 526: static ngx_int_t
! 527: ngx_http_gzip_filter_buffer(ngx_http_gzip_ctx_t *ctx, ngx_chain_t *in)
! 528: {
! 529: size_t size, buffered;
! 530: ngx_buf_t *b, *buf;
! 531: ngx_chain_t *cl, **ll;
! 532: ngx_http_request_t *r;
! 533: ngx_http_gzip_conf_t *conf;
! 534:
! 535: r = ctx->request;
! 536:
! 537: r->connection->buffered |= NGX_HTTP_GZIP_BUFFERED;
! 538:
! 539: buffered = 0;
! 540: ll = &ctx->in;
! 541:
! 542: for (cl = ctx->in; cl; cl = cl->next) {
! 543: buffered += cl->buf->last - cl->buf->pos;
! 544: ll = &cl->next;
! 545: }
! 546:
! 547: conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
! 548:
! 549: while (in) {
! 550: cl = ngx_alloc_chain_link(r->pool);
! 551: if (cl == NULL) {
! 552: return NGX_ERROR;
! 553: }
! 554:
! 555: b = in->buf;
! 556:
! 557: size = b->last - b->pos;
! 558: buffered += size;
! 559:
! 560: if (b->flush || b->last_buf || buffered > conf->postpone_gzipping) {
! 561: ctx->buffering = 0;
! 562: }
! 563:
! 564: if (ctx->buffering && size) {
! 565:
! 566: buf = ngx_create_temp_buf(r->pool, size);
! 567: if (buf == NULL) {
! 568: return NGX_ERROR;
! 569: }
! 570:
! 571: buf->last = ngx_cpymem(buf->pos, b->pos, size);
! 572: b->pos = b->last;
! 573:
! 574: buf->last_buf = b->last_buf;
! 575: buf->tag = (ngx_buf_tag_t) &ngx_http_gzip_filter_module;
! 576:
! 577: cl->buf = buf;
! 578:
! 579: } else {
! 580: cl->buf = b;
! 581: }
! 582:
! 583: *ll = cl;
! 584: ll = &cl->next;
! 585: in = in->next;
! 586: }
! 587:
! 588: *ll = NULL;
! 589:
! 590: return ctx->buffering ? NGX_OK : NGX_DONE;
! 591: }
! 592:
! 593:
! 594: static ngx_int_t
! 595: ngx_http_gzip_filter_deflate_start(ngx_http_request_t *r,
! 596: ngx_http_gzip_ctx_t *ctx)
! 597: {
! 598: int rc;
! 599: ngx_http_gzip_conf_t *conf;
! 600:
! 601: conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
! 602:
! 603: ctx->preallocated = ngx_palloc(r->pool, ctx->allocated);
! 604: if (ctx->preallocated == NULL) {
! 605: return NGX_ERROR;
! 606: }
! 607:
! 608: ctx->free_mem = ctx->preallocated;
! 609:
! 610: ctx->zstream.zalloc = ngx_http_gzip_filter_alloc;
! 611: ctx->zstream.zfree = ngx_http_gzip_filter_free;
! 612: ctx->zstream.opaque = ctx;
! 613:
! 614: rc = deflateInit2(&ctx->zstream, (int) conf->level, Z_DEFLATED,
! 615: - ctx->wbits, ctx->memlevel, Z_DEFAULT_STRATEGY);
! 616:
! 617: if (rc != Z_OK) {
! 618: ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
! 619: "deflateInit2() failed: %d", rc);
! 620: return NGX_ERROR;
! 621: }
! 622:
! 623: r->connection->buffered |= NGX_HTTP_GZIP_BUFFERED;
! 624:
! 625: ctx->last_out = &ctx->out;
! 626: ctx->crc32 = crc32(0L, Z_NULL, 0);
! 627: ctx->flush = Z_NO_FLUSH;
! 628:
! 629: return NGX_OK;
! 630: }
! 631:
! 632:
! 633: static ngx_int_t
! 634: ngx_http_gzip_filter_gzheader(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
! 635: {
! 636: ngx_buf_t *b;
! 637: ngx_chain_t *cl;
! 638: static u_char gzheader[10] =
! 639: { 0x1f, 0x8b, Z_DEFLATED, 0, 0, 0, 0, 0, 0, 3 };
! 640:
! 641: b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
! 642: if (b == NULL) {
! 643: return NGX_ERROR;
! 644: }
! 645:
! 646: b->memory = 1;
! 647: b->pos = gzheader;
! 648: b->last = b->pos + 10;
! 649:
! 650: cl = ngx_alloc_chain_link(r->pool);
! 651: if (cl == NULL) {
! 652: return NGX_ERROR;
! 653: }
! 654:
! 655: cl->buf = b;
! 656: cl->next = ctx->out;
! 657: ctx->out = cl;
! 658:
! 659: ctx->gzheader = 1;
! 660:
! 661: return NGX_OK;
! 662: }
! 663:
! 664:
! 665: static ngx_int_t
! 666: ngx_http_gzip_filter_add_data(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
! 667: {
! 668: if (ctx->zstream.avail_in || ctx->flush != Z_NO_FLUSH || ctx->redo) {
! 669: return NGX_OK;
! 670: }
! 671:
! 672: ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
! 673: "gzip in: %p", ctx->in);
! 674:
! 675: if (ctx->in == NULL) {
! 676: return NGX_DECLINED;
! 677: }
! 678:
! 679: if (ctx->copy_buf) {
! 680:
! 681: /*
! 682: * to avoid CPU cache trashing we do not free() just quit buf,
! 683: * but postpone free()ing after zlib compressing and data output
! 684: */
! 685:
! 686: ctx->copy_buf->next = ctx->copied;
! 687: ctx->copied = ctx->copy_buf;
! 688: ctx->copy_buf = NULL;
! 689: }
! 690:
! 691: ctx->in_buf = ctx->in->buf;
! 692:
! 693: if (ctx->in_buf->tag == (ngx_buf_tag_t) &ngx_http_gzip_filter_module) {
! 694: ctx->copy_buf = ctx->in;
! 695: }
! 696:
! 697: ctx->in = ctx->in->next;
! 698:
! 699: ctx->zstream.next_in = ctx->in_buf->pos;
! 700: ctx->zstream.avail_in = ctx->in_buf->last - ctx->in_buf->pos;
! 701:
! 702: ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
! 703: "gzip in_buf:%p ni:%p ai:%ud",
! 704: ctx->in_buf,
! 705: ctx->zstream.next_in, ctx->zstream.avail_in);
! 706:
! 707: if (ctx->in_buf->last_buf) {
! 708: ctx->flush = Z_FINISH;
! 709:
! 710: } else if (ctx->in_buf->flush) {
! 711: ctx->flush = Z_SYNC_FLUSH;
! 712: }
! 713:
! 714: if (ctx->zstream.avail_in) {
! 715:
! 716: ctx->crc32 = crc32(ctx->crc32, ctx->zstream.next_in,
! 717: ctx->zstream.avail_in);
! 718:
! 719: } else if (ctx->flush == Z_NO_FLUSH) {
! 720: return NGX_AGAIN;
! 721: }
! 722:
! 723: return NGX_OK;
! 724: }
! 725:
! 726:
! 727: static ngx_int_t
! 728: ngx_http_gzip_filter_get_buf(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
! 729: {
! 730: ngx_http_gzip_conf_t *conf;
! 731:
! 732: if (ctx->zstream.avail_out) {
! 733: return NGX_OK;
! 734: }
! 735:
! 736: conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
! 737:
! 738: if (ctx->free) {
! 739: ctx->out_buf = ctx->free->buf;
! 740: ctx->free = ctx->free->next;
! 741:
! 742: } else if (ctx->bufs < conf->bufs.num) {
! 743:
! 744: ctx->out_buf = ngx_create_temp_buf(r->pool, conf->bufs.size);
! 745: if (ctx->out_buf == NULL) {
! 746: return NGX_ERROR;
! 747: }
! 748:
! 749: ctx->out_buf->tag = (ngx_buf_tag_t) &ngx_http_gzip_filter_module;
! 750: ctx->out_buf->recycled = 1;
! 751: ctx->bufs++;
! 752:
! 753: } else {
! 754: ctx->nomem = 1;
! 755: return NGX_DECLINED;
! 756: }
! 757:
! 758: ctx->zstream.next_out = ctx->out_buf->pos;
! 759: ctx->zstream.avail_out = conf->bufs.size;
! 760:
! 761: return NGX_OK;
! 762: }
! 763:
! 764:
! 765: static ngx_int_t
! 766: ngx_http_gzip_filter_deflate(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
! 767: {
! 768: int rc;
! 769: ngx_buf_t *b;
! 770: ngx_chain_t *cl;
! 771: ngx_http_gzip_conf_t *conf;
! 772:
! 773: ngx_log_debug6(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
! 774: "deflate in: ni:%p no:%p ai:%ud ao:%ud fl:%d redo:%d",
! 775: ctx->zstream.next_in, ctx->zstream.next_out,
! 776: ctx->zstream.avail_in, ctx->zstream.avail_out,
! 777: ctx->flush, ctx->redo);
! 778:
! 779: rc = deflate(&ctx->zstream, ctx->flush);
! 780:
! 781: if (rc != Z_OK && rc != Z_STREAM_END && rc != Z_BUF_ERROR) {
! 782: ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
! 783: "deflate() failed: %d, %d", ctx->flush, rc);
! 784: return NGX_ERROR;
! 785: }
! 786:
! 787: ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
! 788: "deflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d",
! 789: ctx->zstream.next_in, ctx->zstream.next_out,
! 790: ctx->zstream.avail_in, ctx->zstream.avail_out,
! 791: rc);
! 792:
! 793: ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
! 794: "gzip in_buf:%p pos:%p",
! 795: ctx->in_buf, ctx->in_buf->pos);
! 796:
! 797: if (ctx->zstream.next_in) {
! 798: ctx->in_buf->pos = ctx->zstream.next_in;
! 799:
! 800: if (ctx->zstream.avail_in == 0) {
! 801: ctx->zstream.next_in = NULL;
! 802: }
! 803: }
! 804:
! 805: ctx->out_buf->last = ctx->zstream.next_out;
! 806:
! 807: if (ctx->zstream.avail_out == 0) {
! 808:
! 809: /* zlib wants to output some more gzipped data */
! 810:
! 811: cl = ngx_alloc_chain_link(r->pool);
! 812: if (cl == NULL) {
! 813: return NGX_ERROR;
! 814: }
! 815:
! 816: cl->buf = ctx->out_buf;
! 817: cl->next = NULL;
! 818: *ctx->last_out = cl;
! 819: ctx->last_out = &cl->next;
! 820:
! 821: ctx->redo = 1;
! 822:
! 823: return NGX_AGAIN;
! 824: }
! 825:
! 826: ctx->redo = 0;
! 827:
! 828: if (ctx->flush == Z_SYNC_FLUSH) {
! 829:
! 830: ctx->flush = Z_NO_FLUSH;
! 831:
! 832: cl = ngx_alloc_chain_link(r->pool);
! 833: if (cl == NULL) {
! 834: return NGX_ERROR;
! 835: }
! 836:
! 837: b = ctx->out_buf;
! 838:
! 839: if (ngx_buf_size(b) == 0) {
! 840:
! 841: b = ngx_calloc_buf(ctx->request->pool);
! 842: if (b == NULL) {
! 843: return NGX_ERROR;
! 844: }
! 845:
! 846: } else {
! 847: ctx->zstream.avail_out = 0;
! 848: }
! 849:
! 850: b->flush = 1;
! 851:
! 852: cl->buf = b;
! 853: cl->next = NULL;
! 854: *ctx->last_out = cl;
! 855: ctx->last_out = &cl->next;
! 856:
! 857: return NGX_OK;
! 858: }
! 859:
! 860: if (rc == Z_STREAM_END) {
! 861:
! 862: if (ngx_http_gzip_filter_deflate_end(r, ctx) != NGX_OK) {
! 863: return NGX_ERROR;
! 864: }
! 865:
! 866: return NGX_OK;
! 867: }
! 868:
! 869: conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
! 870:
! 871: if (conf->no_buffer && ctx->in == NULL) {
! 872:
! 873: cl = ngx_alloc_chain_link(r->pool);
! 874: if (cl == NULL) {
! 875: return NGX_ERROR;
! 876: }
! 877:
! 878: cl->buf = ctx->out_buf;
! 879: cl->next = NULL;
! 880: *ctx->last_out = cl;
! 881: ctx->last_out = &cl->next;
! 882:
! 883: return NGX_OK;
! 884: }
! 885:
! 886: return NGX_AGAIN;
! 887: }
! 888:
! 889:
! 890: static ngx_int_t
! 891: ngx_http_gzip_filter_deflate_end(ngx_http_request_t *r,
! 892: ngx_http_gzip_ctx_t *ctx)
! 893: {
! 894: int rc;
! 895: ngx_buf_t *b;
! 896: ngx_chain_t *cl;
! 897: struct gztrailer *trailer;
! 898:
! 899: ctx->zin = ctx->zstream.total_in;
! 900: ctx->zout = 10 + ctx->zstream.total_out + 8;
! 901:
! 902: rc = deflateEnd(&ctx->zstream);
! 903:
! 904: if (rc != Z_OK) {
! 905: ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
! 906: "deflateEnd() failed: %d", rc);
! 907: return NGX_ERROR;
! 908: }
! 909:
! 910: ngx_pfree(r->pool, ctx->preallocated);
! 911:
! 912: cl = ngx_alloc_chain_link(r->pool);
! 913: if (cl == NULL) {
! 914: return NGX_ERROR;
! 915: }
! 916:
! 917: cl->buf = ctx->out_buf;
! 918: cl->next = NULL;
! 919: *ctx->last_out = cl;
! 920: ctx->last_out = &cl->next;
! 921:
! 922: if (ctx->zstream.avail_out >= 8) {
! 923: trailer = (struct gztrailer *) ctx->out_buf->last;
! 924: ctx->out_buf->last += 8;
! 925: ctx->out_buf->last_buf = 1;
! 926:
! 927: } else {
! 928: b = ngx_create_temp_buf(r->pool, 8);
! 929: if (b == NULL) {
! 930: return NGX_ERROR;
! 931: }
! 932:
! 933: b->last_buf = 1;
! 934:
! 935: cl = ngx_alloc_chain_link(r->pool);
! 936: if (cl == NULL) {
! 937: return NGX_ERROR;
! 938: }
! 939:
! 940: cl->buf = b;
! 941: cl->next = NULL;
! 942: *ctx->last_out = cl;
! 943: ctx->last_out = &cl->next;
! 944: trailer = (struct gztrailer *) b->pos;
! 945: b->last += 8;
! 946: }
! 947:
! 948: #if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED)
! 949:
! 950: trailer->crc32 = ctx->crc32;
! 951: trailer->zlen = ctx->zin;
! 952:
! 953: #else
! 954:
! 955: trailer->crc32[0] = (u_char) (ctx->crc32 & 0xff);
! 956: trailer->crc32[1] = (u_char) ((ctx->crc32 >> 8) & 0xff);
! 957: trailer->crc32[2] = (u_char) ((ctx->crc32 >> 16) & 0xff);
! 958: trailer->crc32[3] = (u_char) ((ctx->crc32 >> 24) & 0xff);
! 959:
! 960: trailer->zlen[0] = (u_char) (ctx->zin & 0xff);
! 961: trailer->zlen[1] = (u_char) ((ctx->zin >> 8) & 0xff);
! 962: trailer->zlen[2] = (u_char) ((ctx->zin >> 16) & 0xff);
! 963: trailer->zlen[3] = (u_char) ((ctx->zin >> 24) & 0xff);
! 964:
! 965: #endif
! 966:
! 967: ctx->zstream.avail_in = 0;
! 968: ctx->zstream.avail_out = 0;
! 969:
! 970: ctx->done = 1;
! 971:
! 972: r->connection->buffered &= ~NGX_HTTP_GZIP_BUFFERED;
! 973:
! 974: return NGX_OK;
! 975: }
! 976:
! 977:
! 978: static void *
! 979: ngx_http_gzip_filter_alloc(void *opaque, u_int items, u_int size)
! 980: {
! 981: ngx_http_gzip_ctx_t *ctx = opaque;
! 982:
! 983: void *p;
! 984: ngx_uint_t alloc;
! 985:
! 986: alloc = items * size;
! 987:
! 988: if (alloc % 512 != 0 && alloc < 8192) {
! 989:
! 990: /*
! 991: * The zlib deflate_state allocation, it takes about 6K,
! 992: * we allocate 8K. Other allocations are divisible by 512.
! 993: */
! 994:
! 995: alloc = 8192;
! 996: }
! 997:
! 998: if (alloc <= ctx->allocated) {
! 999: p = ctx->free_mem;
! 1000: ctx->free_mem += alloc;
! 1001: ctx->allocated -= alloc;
! 1002:
! 1003: ngx_log_debug4(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
! 1004: "gzip alloc: n:%ud s:%ud a:%ud p:%p",
! 1005: items, size, alloc, p);
! 1006:
! 1007: return p;
! 1008: }
! 1009:
! 1010: ngx_log_error(NGX_LOG_ALERT, ctx->request->connection->log, 0,
! 1011: "gzip filter failed to use preallocated memory: %ud of %ud",
! 1012: items * size, ctx->allocated);
! 1013:
! 1014: p = ngx_palloc(ctx->request->pool, items * size);
! 1015:
! 1016: return p;
! 1017: }
! 1018:
! 1019:
! 1020: static void
! 1021: ngx_http_gzip_filter_free(void *opaque, void *address)
! 1022: {
! 1023: #if 0
! 1024: ngx_http_gzip_ctx_t *ctx = opaque;
! 1025:
! 1026: ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
! 1027: "gzip free: %p", address);
! 1028: #endif
! 1029: }
! 1030:
! 1031:
! 1032: static void
! 1033: ngx_http_gzip_filter_free_copy_buf(ngx_http_request_t *r,
! 1034: ngx_http_gzip_ctx_t *ctx)
! 1035: {
! 1036: ngx_chain_t *cl;
! 1037:
! 1038: for (cl = ctx->copied; cl; cl = cl->next) {
! 1039: ngx_pfree(r->pool, cl->buf->start);
! 1040: }
! 1041:
! 1042: ctx->copied = NULL;
! 1043: }
! 1044:
! 1045:
! 1046: static ngx_int_t
! 1047: ngx_http_gzip_add_variables(ngx_conf_t *cf)
! 1048: {
! 1049: ngx_http_variable_t *var;
! 1050:
! 1051: var = ngx_http_add_variable(cf, &ngx_http_gzip_ratio, NGX_HTTP_VAR_NOHASH);
! 1052: if (var == NULL) {
! 1053: return NGX_ERROR;
! 1054: }
! 1055:
! 1056: var->get_handler = ngx_http_gzip_ratio_variable;
! 1057:
! 1058: return NGX_OK;
! 1059: }
! 1060:
! 1061:
! 1062: static ngx_int_t
! 1063: ngx_http_gzip_ratio_variable(ngx_http_request_t *r,
! 1064: ngx_http_variable_value_t *v, uintptr_t data)
! 1065: {
! 1066: ngx_uint_t zint, zfrac;
! 1067: ngx_http_gzip_ctx_t *ctx;
! 1068:
! 1069: v->valid = 1;
! 1070: v->no_cacheable = 0;
! 1071: v->not_found = 0;
! 1072:
! 1073: ctx = ngx_http_get_module_ctx(r, ngx_http_gzip_filter_module);
! 1074:
! 1075: if (ctx == NULL || ctx->zout == 0) {
! 1076: v->not_found = 1;
! 1077: return NGX_OK;
! 1078: }
! 1079:
! 1080: v->data = ngx_pnalloc(r->pool, NGX_INT32_LEN + 3);
! 1081: if (v->data == NULL) {
! 1082: return NGX_ERROR;
! 1083: }
! 1084:
! 1085: zint = (ngx_uint_t) (ctx->zin / ctx->zout);
! 1086: zfrac = (ngx_uint_t) ((ctx->zin * 100 / ctx->zout) % 100);
! 1087:
! 1088: if ((ctx->zin * 1000 / ctx->zout) % 10 > 4) {
! 1089:
! 1090: /* the rounding, e.g., 2.125 to 2.13 */
! 1091:
! 1092: zfrac++;
! 1093:
! 1094: if (zfrac > 99) {
! 1095: zint++;
! 1096: zfrac = 0;
! 1097: }
! 1098: }
! 1099:
! 1100: v->len = ngx_sprintf(v->data, "%ui.%02ui", zint, zfrac) - v->data;
! 1101:
! 1102: return NGX_OK;
! 1103: }
! 1104:
! 1105:
! 1106: static void *
! 1107: ngx_http_gzip_create_conf(ngx_conf_t *cf)
! 1108: {
! 1109: ngx_http_gzip_conf_t *conf;
! 1110:
! 1111: conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_gzip_conf_t));
! 1112: if (conf == NULL) {
! 1113: return NULL;
! 1114: }
! 1115:
! 1116: /*
! 1117: * set by ngx_pcalloc():
! 1118: *
! 1119: * conf->bufs.num = 0;
! 1120: * conf->types = { NULL };
! 1121: * conf->types_keys = NULL;
! 1122: */
! 1123:
! 1124: conf->enable = NGX_CONF_UNSET;
! 1125: conf->no_buffer = NGX_CONF_UNSET;
! 1126:
! 1127: conf->postpone_gzipping = NGX_CONF_UNSET_SIZE;
! 1128: conf->level = NGX_CONF_UNSET;
! 1129: conf->wbits = NGX_CONF_UNSET_SIZE;
! 1130: conf->memlevel = NGX_CONF_UNSET_SIZE;
! 1131: conf->min_length = NGX_CONF_UNSET;
! 1132:
! 1133: return conf;
! 1134: }
! 1135:
! 1136:
! 1137: static char *
! 1138: ngx_http_gzip_merge_conf(ngx_conf_t *cf, void *parent, void *child)
! 1139: {
! 1140: ngx_http_gzip_conf_t *prev = parent;
! 1141: ngx_http_gzip_conf_t *conf = child;
! 1142:
! 1143: ngx_conf_merge_value(conf->enable, prev->enable, 0);
! 1144: ngx_conf_merge_value(conf->no_buffer, prev->no_buffer, 0);
! 1145:
! 1146: ngx_conf_merge_bufs_value(conf->bufs, prev->bufs,
! 1147: (128 * 1024) / ngx_pagesize, ngx_pagesize);
! 1148:
! 1149: ngx_conf_merge_size_value(conf->postpone_gzipping, prev->postpone_gzipping,
! 1150: 0);
! 1151: ngx_conf_merge_value(conf->level, prev->level, 1);
! 1152: ngx_conf_merge_size_value(conf->wbits, prev->wbits, MAX_WBITS);
! 1153: ngx_conf_merge_size_value(conf->memlevel, prev->memlevel,
! 1154: MAX_MEM_LEVEL - 1);
! 1155: ngx_conf_merge_value(conf->min_length, prev->min_length, 20);
! 1156:
! 1157: if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types,
! 1158: &prev->types_keys, &prev->types,
! 1159: ngx_http_html_default_types)
! 1160: != NGX_OK)
! 1161: {
! 1162: return NGX_CONF_ERROR;
! 1163: }
! 1164:
! 1165: return NGX_CONF_OK;
! 1166: }
! 1167:
! 1168:
! 1169: static ngx_int_t
! 1170: ngx_http_gzip_filter_init(ngx_conf_t *cf)
! 1171: {
! 1172: ngx_http_next_header_filter = ngx_http_top_header_filter;
! 1173: ngx_http_top_header_filter = ngx_http_gzip_header_filter;
! 1174:
! 1175: ngx_http_next_body_filter = ngx_http_top_body_filter;
! 1176: ngx_http_top_body_filter = ngx_http_gzip_body_filter;
! 1177:
! 1178: return NGX_OK;
! 1179: }
! 1180:
! 1181:
! 1182: static char *
! 1183: ngx_http_gzip_window(ngx_conf_t *cf, void *post, void *data)
! 1184: {
! 1185: size_t *np = data;
! 1186:
! 1187: size_t wbits, wsize;
! 1188:
! 1189: wbits = 15;
! 1190:
! 1191: for (wsize = 32 * 1024; wsize > 256; wsize >>= 1) {
! 1192:
! 1193: if (wsize == *np) {
! 1194: *np = wbits;
! 1195:
! 1196: return NGX_CONF_OK;
! 1197: }
! 1198:
! 1199: wbits--;
! 1200: }
! 1201:
! 1202: return "must be 512, 1k, 2k, 4k, 8k, 16k, or 32k";
! 1203: }
! 1204:
! 1205:
! 1206: static char *
! 1207: ngx_http_gzip_hash(ngx_conf_t *cf, void *post, void *data)
! 1208: {
! 1209: size_t *np = data;
! 1210:
! 1211: size_t memlevel, hsize;
! 1212:
! 1213: memlevel = 9;
! 1214:
! 1215: for (hsize = 128 * 1024; hsize > 256; hsize >>= 1) {
! 1216:
! 1217: if (hsize == *np) {
! 1218: *np = memlevel;
! 1219:
! 1220: return NGX_CONF_OK;
! 1221: }
! 1222:
! 1223: memlevel--;
! 1224: }
! 1225:
! 1226: return "must be 512, 1k, 2k, 4k, 8k, 16k, 32k, 64k, or 128k";
! 1227: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>