changeset 416:b4f69f2ef02c NGINX_0_7_20

nginx 0.7.20 *) Changes in the ngx_http_gzip_filter_module. *) Feature: the ngx_http_limit_req_module. *) Bugfix: worker processes might exit on a SIGBUS signal on sparc and ppc platforms; the bug had appeared in 0.7.3. Thanks to Maxim Dounin. *) Bugfix: the "proxy_pass http://host/some:uri" directives did not work; the bug had appeared in 0.7.12. *) Bugfix: in HTTPS mode requests might fail with the "bad write retry" error. *) Bugfix: the ngx_http_secure_link_module did not work inside locations, whose names are less than 3 characters. *) Bugfix: $server_addr variable might have no value.
author Igor Sysoev <http://sysoev.ru>
date Mon, 10 Nov 2008 00:00:00 +0300
parents 5410f1e19796
children 735cec38a814
files CHANGES CHANGES.ru auto/modules auto/options auto/sources conf/nginx.conf src/core/nginx.h src/core/ngx_inet.c src/core/ngx_open_file_cache.h src/core/ngx_palloc.c src/core/ngx_resolver.c src/core/ngx_string.c src/event/ngx_event_openssl.c src/http/modules/ngx_http_gzip_filter_module.c src/http/modules/ngx_http_limit_req_module.c src/http/modules/ngx_http_log_module.c src/http/modules/ngx_http_proxy_module.c src/http/modules/ngx_http_secure_link_module.c src/http/modules/perl/nginx.pm src/http/ngx_http.h src/http/ngx_http_core_module.c src/http/ngx_http_postpone_filter_module.c src/http/ngx_http_request.c src/http/ngx_http_request.h src/http/ngx_http_upstream.c src/http/ngx_http_upstream.h
diffstat 26 files changed, 1576 insertions(+), 501 deletions(-) [+]
line wrap: on
line diff
--- a/CHANGES
+++ b/CHANGES
@@ -1,4 +1,26 @@
 
+Changes with nginx 0.7.20                                        10 Nov 2008
+
+    *) Changes in the ngx_http_gzip_filter_module.
+
+    *) Feature: the ngx_http_limit_req_module.
+
+    *) Bugfix: worker processes might exit on a SIGBUS signal on sparc and 
+       ppc platforms; the bug had appeared in 0.7.3.
+       Thanks to Maxim Dounin.
+
+    *) Bugfix: the "proxy_pass http://host/some:uri" directives did not 
+       work; the bug had appeared in 0.7.12.
+
+    *) Bugfix: in HTTPS mode requests might fail with the "bad write retry" 
+       error.
+
+    *) Bugfix: the ngx_http_secure_link_module did not work inside 
+       locations, whose names are less than 3 characters.
+
+    *) Bugfix: $server_addr variable might have no value.
+
+
 Changes with nginx 0.7.19                                        13 Oct 2008
 
     *) Bugfix: version number update.
--- a/CHANGES.ru
+++ b/CHANGES.ru
@@ -1,4 +1,26 @@
 
+Изменения в nginx 0.7.20                                          10.11.2008
+
+    *) Изменения в модуле ngx_http_gzip_filter_module.
+
+    *) Добавление: модуль ngx_http_limit_req_module
+
+    *) Исправление: на платформах sparc и ppc рабочие процессы могли 
+       выходить по сигналу SIGBUS; ошибка появилась в 0.7.3.
+       Спасибо Максиму Дунину.
+
+    *) Исправление: директивы вида "proxy_pass http://host/some:uri" не 
+       работали; ошибка появилась в 0.7.12.
+
+    *) Исправление: при использовании HTTPS запросы могли завершаться с 
+       ошибкой "bad write retry".
+
+    *) Исправление: модуль ngx_http_secure_link_module не работал внутри 
+       location'ов с именами меньше 3 символов.
+
+    *) Исправление: переменная $server_addr могла не иметь значения.
+
+
 Изменения в nginx 0.7.19                                          13.10.2008
 
     *) Исправление: обновление номера версии.
--- a/auto/modules
+++ b/auto/modules
@@ -203,6 +203,11 @@ if [ $HTTP_LIMIT_ZONE = YES ]; then
     HTTP_SRCS="$HTTP_SRCS $HTTP_LIMIT_ZONE_SRCS"
 fi
 
+if [ $HTTP_LIMIT_REQ = YES ]; then
+    HTTP_MODULES="$HTTP_MODULES $HTTP_LIMIT_REQ_MODULE"
+    HTTP_SRCS="$HTTP_SRCS $HTTP_LIMIT_REQ_SRCS"
+fi
+
 if [ $HTTP_REALIP = YES ]; then
     have=NGX_HTTP_REALIP . auto/have
     HTTP_MODULES="$HTTP_MODULES $HTTP_REALIP_MODULE"
--- a/auto/options
+++ b/auto/options
@@ -75,6 +75,7 @@ HTTP_FASTCGI=YES
 HTTP_PERL=NO
 HTTP_MEMCACHED=YES
 HTTP_LIMIT_ZONE=YES
+HTTP_LIMIT_REQ=YES
 HTTP_EMPTY_GIF=YES
 HTTP_BROWSER=YES
 HTTP_SECURE_LINK=NO
@@ -192,6 +193,7 @@ do
         --without-http_fastcgi_module)   HTTP_FASTCGI=NO            ;;
         --without-http_memcached_module) HTTP_MEMCACHED=NO          ;;
         --without-http_limit_zone_module) HTTP_LIMIT_ZONE=NO        ;;
+        --without-http_limit_req_module) HTTP_LIMIT_REQ=NO         ;;
         --without-http_empty_gif_module) HTTP_EMPTY_GIF=NO          ;;
         --without-http_browser_module)   HTTP_BROWSER=NO            ;;
         --without-http_upstream_ip_hash_module) HTTP_UPSTREAM_IP_HASH=NO ;;
@@ -310,6 +312,7 @@ cat << END
   --without-http_fastcgi_module      disable ngx_http_fastcgi_module
   --without-http_memcached_module    disable ngx_http_memcached_module
   --without-http_limit_zone_module   disable ngx_http_limit_zone_module
+  --without-http_limit_req_module    disable ngx_http_limit_req_module
   --without-http_empty_gif_module    disable ngx_http_empty_gif_module
   --without-http_browser_module      disable ngx_http_browser_module
   --without-http_upstream_ip_hash_module
--- a/auto/sources
+++ b/auto/sources
@@ -417,6 +417,10 @@ HTTP_LIMIT_ZONE_MODULE=ngx_http_limit_zo
 HTTP_LIMIT_ZONE_SRCS=src/http/modules/ngx_http_limit_zone_module.c
 
 
+HTTP_LIMIT_REQ_MODULE=ngx_http_limit_req_module
+HTTP_LIMIT_REQ_SRCS=src/http/modules/ngx_http_limit_req_module.c
+
+
 HTTP_EMPTY_GIF_MODULE=ngx_http_empty_gif_module
 HTTP_EMPTY_GIF_SRCS=src/http/modules/ngx_http_empty_gif_module.c
 
--- a/conf/nginx.conf
+++ b/conf/nginx.conf
@@ -18,7 +18,7 @@ http {
     include       mime.types;
     default_type  application/octet-stream;
 
-    #log_format  main  '$remote_addr - $remote_user [$time_local] $request '
+    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
     #                  '"$status" $body_bytes_sent "$http_referer" '
     #                  '"$http_user_agent" "$http_x_forwarded_for"';
 
--- a/src/core/nginx.h
+++ b/src/core/nginx.h
@@ -8,7 +8,7 @@
 #define _NGINX_H_INCLUDED_
 
 
-#define NGINX_VERSION      "0.7.19"
+#define NGINX_VERSION      "0.7.20"
 #define NGINX_VER          "nginx/" NGINX_VERSION
 
 #define NGINX_VAR          "NGINX"
--- a/src/core/ngx_inet.c
+++ b/src/core/ngx_inet.c
@@ -251,7 +251,7 @@ ngx_parse_unix_domain_url(ngx_pool_t *po
 static ngx_int_t
 ngx_parse_inet_url(ngx_pool_t *pool, ngx_url_t *u)
 {
-    u_char          *p, *host, *port, *last, *uri;
+    u_char          *p, *host, *port, *last, *uri, *args;
     size_t           len;
     ngx_int_t        n;
     struct hostent  *h;
@@ -264,7 +264,18 @@ ngx_parse_inet_url(ngx_pool_t *pool, ngx
 
     port = ngx_strlchr(host, last, ':');
 
-    uri = ngx_strlchr(port ? port : host, last, '/');
+    uri = ngx_strlchr(host, last, '/');
+
+    args = ngx_strlchr(host, last, '?');
+
+    if (args) {
+        if (uri == NULL) {
+            uri = args;
+
+        } else if (args < uri) {
+            uri = args;
+        }
+    }
 
     if (uri) {
         if (u->listen || !u->uri_part) {
@@ -276,6 +287,10 @@ ngx_parse_inet_url(ngx_pool_t *pool, ngx
         u->uri.data = uri;
 
         last = uri;
+
+        if (uri < port) {
+            port = NULL;
+        } 
     }
 
     if (port) {
--- a/src/core/ngx_open_file_cache.h
+++ b/src/core/ngx_open_file_cache.h
@@ -12,6 +12,9 @@
 #define _NGX_OPEN_FILE_CACHE_H_INCLUDED_
 
 
+#define NGX_OPEN_FILE_DIRECTIO_OFF  NGX_MAX_OFF_T_VALUE
+
+
 typedef struct {
     ngx_fd_t                 fd;
     ngx_file_uniq_t          uniq;
--- a/src/core/ngx_palloc.c
+++ b/src/core/ngx_palloc.c
@@ -171,6 +171,7 @@ ngx_palloc_block(ngx_pool_t *pool, size_
     new->d.next = NULL;
 
     m += sizeof(ngx_pool_data_t);
+    m = ngx_align_ptr(m, NGX_ALIGNMENT);
     new->d.last = m + size;
 
     current = pool->current;
--- a/src/core/ngx_resolver.c
+++ b/src/core/ngx_resolver.c
@@ -956,11 +956,14 @@ ngx_resolver_process_response(ngx_resolv
 {
     char                  *err;
     size_t                 len;
-    ngx_uint_t             i, ident, flags, code, nqs, nan, qtype, qclass;
+    ngx_uint_t             i, times, ident, qident, flags, code, nqs, nan,
+                           qtype, qclass;
+    ngx_queue_t           *q;
     ngx_resolver_qs_t     *qs;
+    ngx_resolver_node_t   *rn;
     ngx_resolver_query_t  *query;
 
-    if ((size_t) n < sizeof(ngx_resolver_query_t) + 1) {
+    if ((size_t) n < sizeof(ngx_resolver_query_t)) {
         goto short_response;
     }
 
@@ -985,11 +988,31 @@ ngx_resolver_process_response(ngx_resolv
 
     code = flags & 0x7f;
 
-    if (code == NGX_RESOLVE_FORMERR || code > NGX_RESOLVE_REFUSED) {
-        ngx_log_error(r->log_level, r->log, 0,
-                      "DNS error (%ui: %s), query id:%ui",
-                      code, ngx_resolver_strerror(code), ident);
-        return;
+    if (code == NGX_RESOLVE_FORMERR) {
+
+        times = 0;
+
+        for (q = ngx_queue_head(&r->name_resend_queue);
+             q != ngx_queue_sentinel(&r->name_resend_queue) || times++ < 100;
+             q = ngx_queue_next(q))
+        {
+            rn = ngx_queue_data(q, ngx_resolver_node_t, queue);
+            qident = (rn->query[0] << 8) + rn->query[1];
+
+            if (qident == ident) {
+                ngx_log_error(r->log_level, r->log, 0,
+                              "DNS error (%ui: %s), query id:%ui, name:\"%*s\"",
+                              code, ngx_resolver_strerror(code), ident,
+                              rn->nlen, rn->name);
+                return;
+            }
+        }
+
+        goto dns_error;
+    }
+
+    if (code > NGX_RESOLVE_REFUSED) {
+        goto dns_error;
     }
 
     if (nqs != 1) {
@@ -1069,6 +1092,13 @@ done:
     ngx_log_error(r->log_level, r->log, 0, err);
 
     return;
+
+dns_error:
+
+    ngx_log_error(r->log_level, r->log, 0,
+                  "DNS error (%ui: %s), query id:%ui",
+                  code, ngx_resolver_strerror(code), ident);
+    return;
 }
 
 
--- a/src/core/ngx_string.c
+++ b/src/core/ngx_string.c
@@ -8,6 +8,10 @@
 #include <ngx_core.h>
 
 
+static u_char *ngx_sprintf_num(u_char *buf, u_char *last, uint64_t ui64,
+    u_char zero, ngx_uint_t hexadecimal, ngx_uint_t width);
+
+
 void
 ngx_strlow(u_char *dst, u_char *src, size_t n)
 {
@@ -67,6 +71,7 @@ ngx_pstrdup(ngx_pool_t *pool, ngx_str_t 
  *    %[0][width][u][x|X]D      int32_t/uint32_t
  *    %[0][width][u][x|X]L      int64_t/uint64_t
  *    %[0][width|m][u][x|X]A    ngx_atomic_int_t/ngx_atomic_uint_t
+ *    %[0][width][.width]f      float
  *    %P                        ngx_pid_t
  *    %M                        ngx_msec_t
  *    %r                        rlim_t
@@ -118,22 +123,16 @@ ngx_snprintf(u_char *buf, size_t max, co
 u_char *
 ngx_vsnprintf(u_char *buf, size_t max, const char *fmt, va_list args)
 {
-    u_char                *p, zero, *last, temp[NGX_INT64_LEN + 1];
-                                    /*
-                                     * really we need temp[NGX_INT64_LEN] only,
-                                     * but icc issues the warning
-                                     */
+    u_char                *p, zero, *last;
     int                    d;
+    float                  f, scale;
     size_t                 len, slen;
-    uint32_t               ui32;
     int64_t                i64;
     uint64_t               ui64;
     ngx_msec_t             ms;
-    ngx_uint_t             width, sign, hexadecimal, max_width;
+    ngx_uint_t             width, sign, hex, max_width, frac_width, i;
     ngx_str_t             *v;
     ngx_variable_value_t  *vv;
-    static u_char          hex[] = "0123456789abcdef";
-    static u_char          HEX[] = "0123456789ABCDEF";
 
     if (max == 0) {
         return buf;
@@ -156,12 +155,11 @@ ngx_vsnprintf(u_char *buf, size_t max, c
             zero = (u_char) ((*++fmt == '0') ? '0' : ' ');
             width = 0;
             sign = 1;
-            hexadecimal = 0;
+            hex = 0;
             max_width = 0;
+            frac_width = 0;
             slen = (size_t) -1;
 
-            p = temp + NGX_INT64_LEN;
-
             while (*fmt >= '0' && *fmt <= '9') {
                 width = width * 10 + *fmt++ - '0';
             }
@@ -181,17 +179,26 @@ ngx_vsnprintf(u_char *buf, size_t max, c
                     continue;
 
                 case 'X':
-                    hexadecimal = 2;
+                    hex = 2;
                     sign = 0;
                     fmt++;
                     continue;
 
                 case 'x':
-                    hexadecimal = 1;
+                    hex = 1;
                     sign = 0;
                     fmt++;
                     continue;
 
+                case '.':
+                    fmt++;
+
+                    while (*fmt >= '0' && *fmt <= '9') {
+                        frac_width = frac_width * 10 + *fmt++ - '0';
+                    }
+
+                    break;
+
                 case '*':
                     slen = va_arg(args, size_t);
                     fmt++;
@@ -339,6 +346,43 @@ ngx_vsnprintf(u_char *buf, size_t max, c
 
                 break;
 
+            case 'f':
+                f = (float) va_arg(args, double);
+
+                if (f < 0) {
+                    *buf++ = '-';
+                    f = -f;
+                }
+
+                ui64 = (int64_t) f;
+
+                buf = ngx_sprintf_num(buf, last, ui64, zero, 0, width);
+
+                if (frac_width) {
+
+                    if (buf < last) {
+                        *buf++ = '.';
+                    }
+
+                    scale = 1.0;
+
+                    for (i = 0; i < frac_width; i++) {
+                        scale *= 10.0;
+                    }
+
+                    /*
+                     * (int64_t) cast is required for msvc6:
+                     * it can not convert uint64_t to double
+                     */
+                    ui64 = (uint64_t) ((f - (int64_t) ui64) * scale);
+
+                    buf = ngx_sprintf_num(buf, last, ui64, '0', 0, frac_width);
+                }
+
+                fmt++;
+
+                continue;
+
 #if !(NGX_WIN32)
             case 'r':
                 i64 = (int64_t) va_arg(args, rlim_t);
@@ -348,7 +392,7 @@ ngx_vsnprintf(u_char *buf, size_t max, c
 
             case 'p':
                 ui64 = (uintptr_t) va_arg(args, void *);
-                hexadecimal = 2;
+                hex = 2;
                 sign = 0;
                 zero = '0';
                 width = NGX_PTR_SIZE * 2;
@@ -398,63 +442,7 @@ ngx_vsnprintf(u_char *buf, size_t max, c
                 }
             }
 
-            if (hexadecimal == 1) {
-                do {
-
-                    /* the "(uint32_t)" cast disables the BCC's warning */
-                    *--p = hex[(uint32_t) (ui64 & 0xf)];
-
-                } while (ui64 >>= 4);
-
-            } else if (hexadecimal == 2) {
-                do {
-
-                    /* the "(uint32_t)" cast disables the BCC's warning */
-                    *--p = HEX[(uint32_t) (ui64 & 0xf)];
-
-                } while (ui64 >>= 4);
-
-            } else if (ui64 <= NGX_MAX_UINT32_VALUE) {
-
-                /*
-                 * To divide 64-bit number and to find the remainder
-                 * on the x86 platform gcc and icc call the libc functions
-                 * [u]divdi3() and [u]moddi3(), they call another function
-                 * in its turn.  On FreeBSD it is the qdivrem() function,
-                 * its source code is about 170 lines of the code.
-                 * The glibc counterpart is about 150 lines of the code.
-                 *
-                 * For 32-bit numbers and some divisors gcc and icc use
-                 * the inlined multiplication and shifts.  For example,
-                 * unsigned "i32 / 10" is compiled to
-                 *
-                 *     (i32 * 0xCCCCCCCD) >> 35
-                 */
-
-                ui32 = (uint32_t) ui64;
-
-                do {
-                    *--p = (u_char) (ui32 % 10 + '0');
-                } while (ui32 /= 10);
-
-            } else {
-                do {
-                    *--p = (u_char) (ui64 % 10 + '0');
-                } while (ui64 /= 10);
-            }
-
-            len = (temp + NGX_INT64_LEN) - p;
-
-            while (len++ < width && buf < last) {
-                *buf++ = zero;
-            }
-
-            len = (temp + NGX_INT64_LEN) - p;
-            if (buf + len > last) {
-                len = last - buf;
-            }
-
-            buf = ngx_cpymem(buf, p, len);
+            buf = ngx_sprintf_num(buf, last, ui64, zero, hex, width);
 
             fmt++;
 
@@ -467,6 +455,92 @@ ngx_vsnprintf(u_char *buf, size_t max, c
 }
 
 
+static u_char *
+ngx_sprintf_num(u_char *buf, u_char *last, uint64_t ui64, u_char zero,
+    ngx_uint_t hexadecimal, ngx_uint_t width)
+{
+    u_char         *p, temp[NGX_INT64_LEN + 1];
+                       /*
+                        * we need temp[NGX_INT64_LEN] only,
+                        * but icc issues the warning
+                        */
+    size_t          len;
+    uint32_t        ui32;
+    static u_char   hex[] = "0123456789abcdef";
+    static u_char   HEX[] = "0123456789ABCDEF";
+
+    p = temp + NGX_INT64_LEN;
+
+    if (hexadecimal == 0) {
+
+        if (ui64 <= NGX_MAX_UINT32_VALUE) {
+
+            /*
+             * To divide 64-bit numbers and to find remainders
+             * on the x86 platform gcc and icc call the libc functions
+             * [u]divdi3() and [u]moddi3(), they call another function
+             * in its turn.  On FreeBSD it is the qdivrem() function,
+             * its source code is about 170 lines of the code.
+             * The glibc counterpart is about 150 lines of the code.
+             *
+             * For 32-bit numbers and some divisors gcc and icc use
+             * a inlined multiplication and shifts.  For example,
+             * unsigned "i32 / 10" is compiled to
+             *
+             *     (i32 * 0xCCCCCCCD) >> 35
+             */
+
+            ui32 = (uint32_t) ui64;
+
+            do {
+                *--p = (u_char) (ui32 % 10 + '0');
+            } while (ui32 /= 10);
+
+        } else {
+            do {
+                *--p = (u_char) (ui64 % 10 + '0');
+            } while (ui64 /= 10);
+        }
+
+    } else if (hexadecimal == 1) {
+
+        do {
+
+            /* the "(uint32_t)" cast disables the BCC's warning */
+            *--p = hex[(uint32_t) (ui64 & 0xf)];
+
+        } while (ui64 >>= 4);
+
+    } else { /* hexadecimal == 2 */
+
+        do {
+
+            /* the "(uint32_t)" cast disables the BCC's warning */
+            *--p = HEX[(uint32_t) (ui64 & 0xf)];
+
+        } while (ui64 >>= 4);
+    }
+
+    /* zero or space padding */
+
+    len = (temp + NGX_INT64_LEN) - p;
+
+    while (len++ < width && buf < last) {
+        *buf++ = zero;
+    }
+
+    /* number safe copy */
+
+    len = (temp + NGX_INT64_LEN) - p;
+
+    if (buf + len > last) {
+        len = last - buf;
+    }
+
+    return ngx_cpymem(buf, p, len);
+}
+
+
 /*
  * We use ngx_strcasecmp()/ngx_strncasecmp() for 7-bit ASCII strings only,
  * and implement our own ngx_strcasecmp()/ngx_strncasecmp()
--- a/src/event/ngx_event_openssl.c
+++ b/src/event/ngx_event_openssl.c
@@ -188,13 +188,6 @@ ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_
         SSL_CTX_set_options(ssl->ctx, ngx_ssl_protocols[protocols >> 1]);
     }
 
-    /*
-     * we need this option because in ngx_ssl_send_chain()
-     * we may switch to a buffered write and may copy leftover part of
-     * previously unbuffered data to our internal buffer
-     */
-    SSL_CTX_set_mode(ssl->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
-
     SSL_CTX_set_read_ahead(ssl->ctx, 1);
 
     return NGX_OK;
@@ -860,14 +853,7 @@ ngx_ssl_send_chain(ngx_connection_t *c, 
     ssize_t      send, size;
     ngx_buf_t   *buf;
 
-    if (!c->ssl->buffer
-        || (in && in->next == NULL && !(c->buffered & NGX_SSL_BUFFERED)))
-    {
-        /*
-         * we avoid a buffer copy if
-         *     we do not need to buffer the output
-         *     or the incoming buf is a single and our buffer is empty
-         */
+    if (!c->ssl->buffer) {
 
         while (in) {
             if (ngx_buf_special(in->buf)) {
--- a/src/http/modules/ngx_http_gzip_filter_module.c
+++ b/src/http/modules/ngx_http_gzip_filter_module.c
@@ -47,6 +47,8 @@ typedef struct {
     unsigned             flush:4;
     unsigned             redo:1;
     unsigned             done:1;
+    unsigned             nomem:1;
+    unsigned             gzheader:1;
 
     size_t               zin;
     size_t               zout;
@@ -57,10 +59,39 @@ typedef struct {
 } ngx_http_gzip_ctx_t;
 
 
+#if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED)
+
+struct gztrailer {
+    uint32_t  crc32;
+    uint32_t  zlen;
+};
+
+#else /* NGX_HAVE_BIG_ENDIAN || !NGX_HAVE_NONALIGNED */
+
+struct gztrailer {
+    u_char  crc32[4];
+    u_char  zlen[4];
+};
+
+#endif
+
+
+static ngx_int_t ngx_http_gzip_filter_deflate_start(ngx_http_request_t *r,
+    ngx_http_gzip_ctx_t *ctx);
+static ngx_int_t ngx_http_gzip_filter_gzheader(ngx_http_request_t *r,
+    ngx_http_gzip_ctx_t *ctx);
+static ngx_int_t ngx_http_gzip_filter_add_data(ngx_http_request_t *r,
+    ngx_http_gzip_ctx_t *ctx);
+static ngx_int_t ngx_http_gzip_filter_get_buf(ngx_http_request_t *r,
+    ngx_http_gzip_ctx_t *ctx);
+static ngx_int_t ngx_http_gzip_filter_deflate(ngx_http_request_t *r,
+    ngx_http_gzip_ctx_t *ctx);
+static ngx_int_t ngx_http_gzip_filter_deflate_end(ngx_http_request_t *r,
+    ngx_http_gzip_ctx_t *ctx);
+
 static void *ngx_http_gzip_filter_alloc(void *opaque, u_int items,
     u_int size);
 static void ngx_http_gzip_filter_free(void *opaque, void *address);
-static void ngx_http_gzip_error(ngx_http_gzip_ctx_t *ctx);
 
 static ngx_int_t ngx_http_gzip_add_variables(ngx_conf_t *cf);
 static ngx_int_t ngx_http_gzip_ratio_variable(ngx_http_request_t *r,
@@ -176,25 +207,6 @@ ngx_module_t  ngx_http_gzip_filter_modul
 };
 
 
-static u_char  gzheader[10] = { 0x1f, 0x8b, Z_DEFLATED, 0, 0, 0, 0, 0, 0, 3 };
-
-#if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED)
-
-struct gztrailer {
-    uint32_t  crc32;
-    uint32_t  zlen;
-};
-
-#else /* NGX_HAVE_BIG_ENDIAN || !NGX_HAVE_NONALIGNED */
-
-struct gztrailer {
-    u_char  crc32[4];
-    u_char  zlen[4];
-};
-
-#endif
-
-
 static ngx_str_t  ngx_http_gzip_ratio = ngx_string("gzip_ratio");
 
 static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
@@ -261,13 +273,9 @@ ngx_http_gzip_header_filter(ngx_http_req
 static ngx_int_t
 ngx_http_gzip_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
 {
-    int                    rc, wbits, memlevel;
-    ngx_int_t              last;
-    struct gztrailer      *trailer;
-    ngx_buf_t             *b;
-    ngx_chain_t           *cl, out;
-    ngx_http_gzip_ctx_t   *ctx;
-    ngx_http_gzip_conf_t  *conf;
+    int                   rc;
+    ngx_chain_t          *cl;
+    ngx_http_gzip_ctx_t  *ctx;
 
     ctx = ngx_http_get_module_ctx(r, ngx_http_gzip_filter_module);
 
@@ -275,392 +283,498 @@ ngx_http_gzip_body_filter(ngx_http_reque
         return ngx_http_next_body_filter(r, in);
     }
 
-    conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
-
     if (ctx->preallocated == NULL) {
-        wbits = conf->wbits;
-        memlevel = conf->memlevel;
-
-        if (ctx->length > 0) {
-
-            /* the actual zlib window size is smaller by 262 bytes */
-
-            while (ctx->length < ((1 << (wbits - 1)) - 262)) {
-                wbits--;
-                memlevel--;
-            }
-        }
-
-        /*
-         * We preallocate a memory for zlib in one buffer (200K-400K), this
-         * decreases a number of malloc() and free() calls and also probably
-         * decreases a number of syscalls (sbrk() and so on).
-         * Besides we free this memory as soon as the gzipping will complete
-         * and do not wait while a whole response will be sent to a client.
-         *
-         * 8K is for zlib deflate_state, it takes
-         *  *) 5816 bytes on i386 and sparc64 (32-bit mode)
-         *  *) 5920 bytes on amd64 and sparc64
-         */
-
-        ctx->allocated = 8192 + (1 << (wbits + 2)) + (1 << (memlevel + 9));
-
-        ctx->preallocated = ngx_palloc(r->pool, ctx->allocated);
-        if (ctx->preallocated == NULL) {
-            return NGX_ERROR;
+        if (ngx_http_gzip_filter_deflate_start(r, ctx) != NGX_OK) {
+            goto failed;
         }
-
-        ctx->free_mem = ctx->preallocated;
-
-        ctx->zstream.zalloc = ngx_http_gzip_filter_alloc;
-        ctx->zstream.zfree = ngx_http_gzip_filter_free;
-        ctx->zstream.opaque = ctx;
-
-        rc = deflateInit2(&ctx->zstream, (int) conf->level, Z_DEFLATED,
-                          -wbits, memlevel, Z_DEFAULT_STRATEGY);
-
-        if (rc != Z_OK) {
-            ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
-                          "deflateInit2() failed: %d", rc);
-            ngx_http_gzip_error(ctx);
-            return NGX_ERROR;
-        }
-
-        b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
-        if (b == NULL) {
-            ngx_http_gzip_error(ctx);
-            return NGX_ERROR;
-        }
-
-        b->memory = 1;
-        b->pos = gzheader;
-        b->last = b->pos + 10;
-
-        out.buf = b;
-        out.next = NULL;
-
-        /*
-         * We pass the gzheader to the next filter now to avoid its linking
-         * to the ctx->busy chain.  zlib does not usually output the compressed
-         * data in the initial iterations, so the gzheader that was linked
-         * to the ctx->busy chain would be flushed by ngx_http_write_filter().
-         */
-
-        if (ngx_http_next_body_filter(r, &out) == NGX_ERROR) {
-            ngx_http_gzip_error(ctx);
-            return NGX_ERROR;
-        }
-
-        r->connection->buffered |= NGX_HTTP_GZIP_BUFFERED;
-
-        ctx->last_out = &ctx->out;
-
-        ctx->crc32 = crc32(0L, Z_NULL, 0);
-        ctx->flush = Z_NO_FLUSH;
     }
 
     if (in) {
         if (ngx_chain_add_copy(r->pool, &ctx->in, in) == NGX_ERROR) {
-            ngx_http_gzip_error(ctx);
-            return NGX_ERROR;
+            goto failed;
         }
     }
 
-    last = NGX_NONE;
+    if (ctx->nomem) {
+
+        /* flush busy buffers */
+
+        if (ngx_http_next_body_filter(r, NULL) == NGX_ERROR) {
+            goto failed;
+        }
+
+        cl = NULL;
+
+        ngx_chain_update_chains(&ctx->free, &ctx->busy, &cl,
+                                (ngx_buf_tag_t) &ngx_http_gzip_filter_module);
+        ctx->nomem = 0;
+    }
 
     for ( ;; ) {
 
+        /* cycle while we can write to a client */
+
         for ( ;; ) {
 
-            /* does zlib need a new data ? */
-
-            if (ctx->zstream.avail_in == 0
-                && ctx->flush == Z_NO_FLUSH
-                && !ctx->redo)
-            {
-                ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
-                               "gzip in: %p", ctx->in);
+            /* cycle while there is data to feed zlib and ... */
 
-                if (ctx->in == NULL) {
-                    break;
-                }
-
-                ctx->in_buf = ctx->in->buf;
-                ctx->in = ctx->in->next;
-
-                ctx->zstream.next_in = ctx->in_buf->pos;
-                ctx->zstream.avail_in = ctx->in_buf->last - ctx->in_buf->pos;
-
-                ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
-                               "gzip in_buf:%p ni:%p ai:%ud",
-                               ctx->in_buf,
-                               ctx->zstream.next_in, ctx->zstream.avail_in);
+            rc = ngx_http_gzip_filter_add_data(r, ctx);
 
-                /* STUB */
-                if (ctx->in_buf->last < ctx->in_buf->pos) {
-                    ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
-                                  "zstream.avail_in is huge");
-                    ctx->done = 1;
-                    return NGX_ERROR;
-                }
-                /**/
-
-                if (ctx->in_buf->last_buf) {
-                    ctx->flush = Z_FINISH;
+            if (rc == NGX_DECLINED) {
+                break;
+            }
 
-                } else if (ctx->in_buf->flush) {
-                    ctx->flush = Z_SYNC_FLUSH;
-                }
-
-                if (ctx->zstream.avail_in == 0) {
-                    if (ctx->flush == Z_NO_FLUSH) {
-                        continue;
-                    }
-
-                } else {
-                    ctx->crc32 = crc32(ctx->crc32, ctx->zstream.next_in,
-                                       ctx->zstream.avail_in);
-                }
+            if (rc == NGX_AGAIN) {
+                continue;
             }
 
 
-            /* is there a space for the gzipped data ? */
-
-            if (ctx->zstream.avail_out == 0) {
-
-                if (ctx->free) {
-                    ctx->out_buf = ctx->free->buf;
-                    ctx->free = ctx->free->next;
-
-                } else if (ctx->bufs < conf->bufs.num) {
-                    ctx->out_buf = ngx_create_temp_buf(r->pool,
-                                                       conf->bufs.size);
-                    if (ctx->out_buf == NULL) {
-                        ngx_http_gzip_error(ctx);
-                        return NGX_ERROR;
-                    }
-
-                    ctx->out_buf->tag = (ngx_buf_tag_t)
-                                                  &ngx_http_gzip_filter_module;
-                    ctx->out_buf->recycled = 1;
-                    ctx->bufs++;
+            /* ... there are buffers to write zlib output */
 
-                } else {
-                    break;
-                }
-
-                ctx->zstream.next_out = ctx->out_buf->pos;
-                ctx->zstream.avail_out = conf->bufs.size;
-            }
+            rc = ngx_http_gzip_filter_get_buf(r, ctx);
 
-            ngx_log_debug6(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
-                         "deflate in: ni:%p no:%p ai:%ud ao:%ud fl:%d redo:%d",
-                         ctx->zstream.next_in, ctx->zstream.next_out,
-                         ctx->zstream.avail_in, ctx->zstream.avail_out,
-                         ctx->flush, ctx->redo);
-
-            rc = deflate(&ctx->zstream, ctx->flush);
-
-            if (rc != Z_OK && rc != Z_STREAM_END) {
-                ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
-                              "deflate() failed: %d, %d", ctx->flush, rc);
-                ngx_http_gzip_error(ctx);
-                return NGX_ERROR;
+            if (rc == NGX_DECLINED) {
+                break;
             }
 
-            ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
-                           "deflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d",
-                           ctx->zstream.next_in, ctx->zstream.next_out,
-                           ctx->zstream.avail_in, ctx->zstream.avail_out,
-                           rc);
-
-            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
-                           "gzip in_buf:%p pos:%p",
-                           ctx->in_buf, ctx->in_buf->pos);
-
-
-            if (ctx->zstream.next_in) {
-                ctx->in_buf->pos = ctx->zstream.next_in;
-
-                if (ctx->zstream.avail_in == 0) {
-                    ctx->zstream.next_in = NULL;
-                }
+            if (rc == NGX_ERROR) {
+                goto failed;
             }
 
-            ctx->out_buf->last = ctx->zstream.next_out;
-
-            if (ctx->zstream.avail_out == 0) {
-
-                /* zlib wants to output some more gzipped data */
-
-                cl = ngx_alloc_chain_link(r->pool);
-                if (cl == NULL) {
-                    ngx_http_gzip_error(ctx);
-                    return NGX_ERROR;
-                }
-
-                cl->buf = ctx->out_buf;
-                cl->next = NULL;
-                *ctx->last_out = cl;
-                ctx->last_out = &cl->next;
-
-                ctx->redo = 1;
 
-                continue;
-            }
-
-            ctx->redo = 0;
-
-            if (ctx->flush == Z_SYNC_FLUSH) {
-
-                ctx->zstream.avail_out = 0;
-                ctx->out_buf->flush = 1;
-                ctx->flush = Z_NO_FLUSH;
+            rc = ngx_http_gzip_filter_deflate(r, ctx);
 
-                cl = ngx_alloc_chain_link(r->pool);
-                if (cl == NULL) {
-                    ngx_http_gzip_error(ctx);
-                    return NGX_ERROR;
-                }
-
-                cl->buf = ctx->out_buf;
-                cl->next = NULL;
-                *ctx->last_out = cl;
-                ctx->last_out = &cl->next;
-
+            if (rc == NGX_OK) {
                 break;
             }
 
-            if (rc == Z_STREAM_END) {
-
-                ctx->zin = ctx->zstream.total_in;
-                ctx->zout = 10 + ctx->zstream.total_out + 8;
-
-                rc = deflateEnd(&ctx->zstream);
-
-                if (rc != Z_OK) {
-                    ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
-                                  "deflateEnd() failed: %d", rc);
-                    ngx_http_gzip_error(ctx);
-                    return NGX_ERROR;
-                }
-
-                ngx_pfree(r->pool, ctx->preallocated);
-
-                cl = ngx_alloc_chain_link(r->pool);
-                if (cl == NULL) {
-                    ngx_http_gzip_error(ctx);
-                    return NGX_ERROR;
-                }
-
-                cl->buf = ctx->out_buf;
-                cl->next = NULL;
-                *ctx->last_out = cl;
-                ctx->last_out = &cl->next;
-
-                if (ctx->zstream.avail_out >= 8) {
-                    trailer = (struct gztrailer *) ctx->out_buf->last;
-                    ctx->out_buf->last += 8;
-                    ctx->out_buf->last_buf = 1;
-
-                } else {
-                    b = ngx_create_temp_buf(r->pool, 8);
-                    if (b == NULL) {
-                        ngx_http_gzip_error(ctx);
-                        return NGX_ERROR;
-                    }
-
-                    b->last_buf = 1;
-
-                    cl = ngx_alloc_chain_link(r->pool);
-                    if (cl == NULL) {
-                        ngx_http_gzip_error(ctx);
-                        return NGX_ERROR;
-                    }
-
-                    cl->buf = b;
-                    cl->next = NULL;
-                    *ctx->last_out = cl;
-                    ctx->last_out = &cl->next;
-                    trailer = (struct gztrailer *) b->pos;
-                    b->last += 8;
-                }
-
-#if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED)
-
-                trailer->crc32 = ctx->crc32;
-                trailer->zlen = ctx->zin;
-
-#else
-                trailer->crc32[0] = (u_char) (ctx->crc32 & 0xff);
-                trailer->crc32[1] = (u_char) ((ctx->crc32 >> 8) & 0xff);
-                trailer->crc32[2] = (u_char) ((ctx->crc32 >> 16) & 0xff);
-                trailer->crc32[3] = (u_char) ((ctx->crc32 >> 24) & 0xff);
-
-                trailer->zlen[0] = (u_char) (ctx->zin & 0xff);
-                trailer->zlen[1] = (u_char) ((ctx->zin >> 8) & 0xff);
-                trailer->zlen[2] = (u_char) ((ctx->zin >> 16) & 0xff);
-                trailer->zlen[3] = (u_char) ((ctx->zin >> 24) & 0xff);
-#endif
-
-                ctx->zstream.avail_in = 0;
-                ctx->zstream.avail_out = 0;
-
-                ctx->done = 1;
-
-                r->connection->buffered &= ~NGX_HTTP_GZIP_BUFFERED;
-
-                break;
+            if (rc == NGX_ERROR) {
+                goto failed;
             }
 
-            if (conf->no_buffer && ctx->in == NULL) {
+            /* rc == NGX_AGAIN */
+        }
 
-                cl = ngx_alloc_chain_link(r->pool);
-                if (cl == NULL) {
-                    ngx_http_gzip_error(ctx);
-                    return NGX_ERROR;
-                }
+        if (ctx->out == NULL) {
+            return ctx->busy ? NGX_AGAIN : NGX_OK;
+        }
 
-                cl->buf = ctx->out_buf;
-                cl->next = NULL;
-                *ctx->last_out = cl;
-                ctx->last_out = &cl->next;
-
-                break;
+        if (!ctx->gzheader) {
+            if (ngx_http_gzip_filter_gzheader(r, ctx) != NGX_OK) {
+                goto failed;
             }
         }
 
-        if (ctx->out == NULL) {
-
-            if (last == NGX_AGAIN) {
-                return NGX_AGAIN;
-            }
-
-            if (ctx->busy == NULL) {
-                return NGX_OK;
-            }
-        }
+        rc = ngx_http_next_body_filter(r, ctx->out);
 
-        last = ngx_http_next_body_filter(r, ctx->out);
-
-        /*
-         * we do not check NGX_AGAIN here because the downstream filters
-         * may free some buffers and zlib may compress some data into them
-         */
-
-        if (last == NGX_ERROR) {
-            ngx_http_gzip_error(ctx);
-            return NGX_ERROR;
+        if (rc == NGX_ERROR) {
+            goto failed;
         }
 
         ngx_chain_update_chains(&ctx->free, &ctx->busy, &ctx->out,
                                 (ngx_buf_tag_t) &ngx_http_gzip_filter_module);
         ctx->last_out = &ctx->out;
 
+        ctx->nomem = 0;
+
         if (ctx->done) {
-            return last;
+            return rc;
+        }
+    }
+
+    /* unreachable */
+
+failed:
+
+    ctx->done = 1;
+
+    if (ctx->preallocated) {
+        deflateEnd(&ctx->zstream);
+
+        ngx_pfree(r->pool, ctx->preallocated);
+    }
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_deflate_start(ngx_http_request_t *r,
+    ngx_http_gzip_ctx_t *ctx)
+{
+    int                    rc, wbits, memlevel;
+    ngx_http_gzip_conf_t  *conf;
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
+
+    wbits = conf->wbits;
+    memlevel = conf->memlevel;
+
+    if (ctx->length > 0) {
+
+        /* the actual zlib window size is smaller by 262 bytes */
+
+        while (ctx->length < ((1 << (wbits - 1)) - 262)) {
+            wbits--;
+            memlevel--;
         }
     }
+
+    /*
+     * We preallocate a memory for zlib in one buffer (200K-400K), this
+     * decreases a number of malloc() and free() calls and also probably
+     * decreases a number of syscalls (sbrk()/mmap() and so on).
+     * Besides we free the memory as soon as a gzipping will complete
+     * and do not wait while a whole response will be sent to a client.
+     *
+     * 8K is for zlib deflate_state, it takes
+     *  *) 5816 bytes on i386 and sparc64 (32-bit mode)
+     *  *) 5920 bytes on amd64 and sparc64
+     */
+
+    ctx->allocated = 8192 + (1 << (wbits + 2)) + (1 << (memlevel + 9));
+
+    ctx->preallocated = ngx_palloc(r->pool, ctx->allocated);
+    if (ctx->preallocated == NULL) {
+        return NGX_ERROR;
+    }
+
+    ctx->free_mem = ctx->preallocated;
+
+    ctx->zstream.zalloc = ngx_http_gzip_filter_alloc;
+    ctx->zstream.zfree = ngx_http_gzip_filter_free;
+    ctx->zstream.opaque = ctx;
+
+    rc = deflateInit2(&ctx->zstream, (int) conf->level, Z_DEFLATED,
+                      -wbits, memlevel, Z_DEFAULT_STRATEGY);
+
+    if (rc != Z_OK) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                      "deflateInit2() failed: %d", rc);
+        return NGX_ERROR;
+    }
+
+    r->connection->buffered |= NGX_HTTP_GZIP_BUFFERED;
+
+    ctx->last_out = &ctx->out;
+    ctx->crc32 = crc32(0L, Z_NULL, 0);
+    ctx->flush = Z_NO_FLUSH;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_gzheader(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
+{
+    ngx_buf_t      *b;
+    ngx_chain_t    *cl;
+    static u_char  gzheader[10] =
+                               { 0x1f, 0x8b, Z_DEFLATED, 0, 0, 0, 0, 0, 0, 3 };
+
+    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
+    if (b == NULL) {
+        return NGX_ERROR;
+    }
+
+    b->memory = 1;
+    b->pos = gzheader;
+    b->last = b->pos + 10;
+
+    cl = ngx_alloc_chain_link(r->pool);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    cl->buf = b;
+    cl->next = ctx->out;
+    ctx->out = cl;
+
+    ctx->gzheader = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_add_data(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
+{
+    if (ctx->zstream.avail_in || ctx->flush != Z_NO_FLUSH || ctx->redo) {
+        return NGX_OK;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "gzip in: %p", ctx->in);
+
+    if (ctx->in == NULL) {
+        return NGX_DECLINED;
+    }
+
+    ctx->in_buf = ctx->in->buf;
+    ctx->in = ctx->in->next;
+
+    ctx->zstream.next_in = ctx->in_buf->pos;
+    ctx->zstream.avail_in = ctx->in_buf->last - ctx->in_buf->pos;
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "gzip in_buf:%p ni:%p ai:%ud",
+                   ctx->in_buf,
+                   ctx->zstream.next_in, ctx->zstream.avail_in);
+
+    if (ctx->in_buf->last_buf) {
+        ctx->flush = Z_FINISH;
+
+    } else if (ctx->in_buf->flush) {
+        ctx->flush = Z_SYNC_FLUSH;
+    }
+
+    if (ctx->zstream.avail_in) {
+
+        ctx->crc32 = crc32(ctx->crc32, ctx->zstream.next_in,
+                           ctx->zstream.avail_in);
+
+    } else if (ctx->flush == Z_NO_FLUSH) {
+        return NGX_AGAIN;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_get_buf(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
+{
+    ngx_http_gzip_conf_t  *conf;
+
+    if (ctx->zstream.avail_out) {
+        return NGX_OK;
+    }
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
+
+    if (ctx->free) {
+        ctx->out_buf = ctx->free->buf;
+        ctx->free = ctx->free->next;
+
+    } else if (ctx->bufs < conf->bufs.num) {
+
+        ctx->out_buf = ngx_create_temp_buf(r->pool, conf->bufs.size);
+        if (ctx->out_buf == NULL) {
+            return NGX_ERROR;
+        }
+
+        ctx->out_buf->tag = (ngx_buf_tag_t) &ngx_http_gzip_filter_module;
+        ctx->out_buf->recycled = 1;
+        ctx->bufs++;
+
+    } else {
+        ctx->nomem = 1;
+        return NGX_DECLINED;
+    }
+
+    ctx->zstream.next_out = ctx->out_buf->pos;
+    ctx->zstream.avail_out = conf->bufs.size;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_deflate(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
+{
+    int                    rc;
+    ngx_chain_t           *cl;
+    ngx_http_gzip_conf_t  *conf;
+
+    ngx_log_debug6(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                 "deflate in: ni:%p no:%p ai:%ud ao:%ud fl:%d redo:%d",
+                 ctx->zstream.next_in, ctx->zstream.next_out,
+                 ctx->zstream.avail_in, ctx->zstream.avail_out,
+                 ctx->flush, ctx->redo);
+
+    rc = deflate(&ctx->zstream, ctx->flush);
+
+    if (rc != Z_OK && rc != Z_STREAM_END) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                      "deflate() failed: %d, %d", ctx->flush, rc);
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "deflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d",
+                   ctx->zstream.next_in, ctx->zstream.next_out,
+                   ctx->zstream.avail_in, ctx->zstream.avail_out,
+                   rc);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "gzip in_buf:%p pos:%p",
+                   ctx->in_buf, ctx->in_buf->pos);
+
+    if (ctx->zstream.next_in) {
+        ctx->in_buf->pos = ctx->zstream.next_in;
+
+        if (ctx->zstream.avail_in == 0) {
+            ctx->zstream.next_in = NULL;
+        }
+    }
+
+    ctx->out_buf->last = ctx->zstream.next_out;
+
+    if (ctx->zstream.avail_out == 0) {
+
+        /* zlib wants to output some more gzipped data */
+
+        cl = ngx_alloc_chain_link(r->pool);
+        if (cl == NULL) {
+            return NGX_ERROR;
+        }
+
+        cl->buf = ctx->out_buf;
+        cl->next = NULL;
+        *ctx->last_out = cl;
+        ctx->last_out = &cl->next;
+
+        ctx->redo = 1;
+
+        return NGX_AGAIN;
+    }
+
+    ctx->redo = 0;
+
+    if (ctx->flush == Z_SYNC_FLUSH) {
+
+        ctx->zstream.avail_out = 0;
+        ctx->out_buf->flush = 1;
+        ctx->flush = Z_NO_FLUSH;
+
+        cl = ngx_alloc_chain_link(r->pool);
+        if (cl == NULL) {
+            return NGX_ERROR;
+        }
+
+        cl->buf = ctx->out_buf;
+        cl->next = NULL;
+        *ctx->last_out = cl;
+        ctx->last_out = &cl->next;
+
+        return NGX_OK;
+    }
+
+    if (rc == Z_STREAM_END) {
+
+        if (ngx_http_gzip_filter_deflate_end(r, ctx) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        return NGX_OK;
+    }
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
+
+    if (conf->no_buffer && ctx->in == NULL) {
+
+        cl = ngx_alloc_chain_link(r->pool);
+        if (cl == NULL) {
+            return NGX_ERROR;
+        }
+
+        cl->buf = ctx->out_buf;
+        cl->next = NULL;
+        *ctx->last_out = cl;
+        ctx->last_out = &cl->next;
+
+        return NGX_OK;
+    }
+
+    return NGX_AGAIN;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_deflate_end(ngx_http_request_t *r,
+    ngx_http_gzip_ctx_t *ctx)
+{
+    int                rc;
+    ngx_buf_t         *b;
+    ngx_chain_t       *cl;
+    struct gztrailer  *trailer;
+
+    ctx->zin = ctx->zstream.total_in;
+    ctx->zout = 10 + ctx->zstream.total_out + 8;
+
+    rc = deflateEnd(&ctx->zstream);
+
+    if (rc != Z_OK) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                      "deflateEnd() failed: %d", rc);
+        return NGX_ERROR;
+    }
+
+    ngx_pfree(r->pool, ctx->preallocated);
+
+    cl = ngx_alloc_chain_link(r->pool);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    cl->buf = ctx->out_buf;
+    cl->next = NULL;
+    *ctx->last_out = cl;
+    ctx->last_out = &cl->next;
+
+    if (ctx->zstream.avail_out >= 8) {
+        trailer = (struct gztrailer *) ctx->out_buf->last;
+        ctx->out_buf->last += 8;
+        ctx->out_buf->last_buf = 1;
+
+    } else {
+        b = ngx_create_temp_buf(r->pool, 8);
+        if (b == NULL) {
+            return NGX_ERROR;
+        }
+
+        b->last_buf = 1;
+
+        cl = ngx_alloc_chain_link(r->pool);
+        if (cl == NULL) {
+            return NGX_ERROR;
+        }
+
+        cl->buf = b;
+        cl->next = NULL;
+        *ctx->last_out = cl;
+        ctx->last_out = &cl->next;
+        trailer = (struct gztrailer *) b->pos;
+        b->last += 8;
+    }
+
+#if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED)
+
+    trailer->crc32 = ctx->crc32;
+    trailer->zlen = ctx->zin;
+
+#else
+
+    trailer->crc32[0] = (u_char) (ctx->crc32 & 0xff);
+    trailer->crc32[1] = (u_char) ((ctx->crc32 >> 8) & 0xff);
+    trailer->crc32[2] = (u_char) ((ctx->crc32 >> 16) & 0xff);
+    trailer->crc32[3] = (u_char) ((ctx->crc32 >> 24) & 0xff);
+
+    trailer->zlen[0] = (u_char) (ctx->zin & 0xff);
+    trailer->zlen[1] = (u_char) ((ctx->zin >> 8) & 0xff);
+    trailer->zlen[2] = (u_char) ((ctx->zin >> 16) & 0xff);
+    trailer->zlen[3] = (u_char) ((ctx->zin >> 24) & 0xff);
+
+#endif
+
+    ctx->zstream.avail_in = 0;
+    ctx->zstream.avail_out = 0;
+
+    ctx->done = 1;
+
+    r->connection->buffered &= ~NGX_HTTP_GZIP_BUFFERED;
+
+    return NGX_OK;
 }
 
 
@@ -718,24 +832,6 @@ ngx_http_gzip_filter_free(void *opaque, 
 }
 
 
-static void
-ngx_http_gzip_error(ngx_http_gzip_ctx_t *ctx)
-{
-    deflateEnd(&ctx->zstream);
-
-    if (ctx->preallocated) {
-        ngx_pfree(ctx->request->pool, ctx->preallocated);
-    }
-
-    ctx->zstream.avail_in = 0;
-    ctx->zstream.avail_out = 0;
-
-    ctx->done = 1;
-
-    return;
-}
-
-
 static ngx_int_t
 ngx_http_gzip_add_variables(ngx_conf_t *cf)
 {
@@ -871,9 +967,9 @@ ngx_http_gzip_filter_init(ngx_conf_t *cf
 static char *
 ngx_http_gzip_window(ngx_conf_t *cf, void *post, void *data)
 {
-    int *np = data;
+    size_t *np = data;
 
-    int  wbits, wsize;
+    size_t  wbits, wsize;
 
     wbits = 15;
 
@@ -895,9 +991,9 @@ ngx_http_gzip_window(ngx_conf_t *cf, voi
 static char *
 ngx_http_gzip_hash(ngx_conf_t *cf, void *post, void *data)
 {
-    int *np = data;
+    size_t *np = data;
 
-    int  memlevel, hsize;
+    size_t  memlevel, hsize;
 
     memlevel = 9;
 
new file mode 100644
--- /dev/null
+++ b/src/http/modules/ngx_http_limit_req_module.c
@@ -0,0 +1,794 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    u_char              color;
+    u_char              dummy;
+    u_short             len;
+    ngx_queue_t         queue;
+    ngx_msec_t          last;
+    float               rate;
+    u_char              data[1];
+} ngx_http_limit_req_node_t;
+
+
+typedef struct {
+    ngx_rbtree_t       *rbtree;
+    ngx_queue_t        *queue;
+    ngx_slab_pool_t    *shpool;
+    float               rate;
+    ngx_int_t           index;
+    ngx_str_t           var;
+} ngx_http_limit_req_ctx_t;
+
+
+typedef struct {
+    ngx_shm_zone_t     *shm_zone;
+    float               burst;
+    ngx_msec_t          delay;
+} ngx_http_limit_req_conf_t;
+
+
+static void ngx_http_limit_req_delay(ngx_http_request_t *r);
+static ngx_int_t ngx_http_limit_req_lookup(ngx_http_limit_req_conf_t *lzcf,
+    ngx_uint_t hash, u_char *data, size_t len, ngx_http_limit_req_node_t **lzp);
+static void ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx,
+    ngx_uint_t n);
+
+static void *ngx_http_limit_req_create_conf(ngx_conf_t *cf);
+static char *ngx_http_limit_req_merge_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static char *ngx_http_limit_req_zone(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static char *ngx_http_limit_req(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static ngx_int_t ngx_http_limit_req_init(ngx_conf_t *cf);
+
+
+static ngx_command_t  ngx_http_limit_req_commands[] = {
+
+    { ngx_string("limit_req_zone"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE3,
+      ngx_http_limit_req_zone,
+      0,
+      0,
+      NULL },
+
+    { ngx_string("limit_req"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
+      ngx_http_limit_req,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_limit_req_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_limit_req_init,               /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_limit_req_create_conf,        /* create location configration */
+    ngx_http_limit_req_merge_conf          /* merge location configration */
+};
+
+
+ngx_module_t  ngx_http_limit_req_module = {
+    NGX_MODULE_V1,
+    &ngx_http_limit_req_module_ctx,        /* module context */
+    ngx_http_limit_req_commands,           /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_limit_req_handler(ngx_http_request_t *r)
+{
+    float                       rate;
+    size_t                      len, n;
+    uint32_t                    hash;
+    ngx_int_t                   rc;
+    ngx_time_t                 *tp;
+    ngx_rbtree_node_t          *node;
+    ngx_http_variable_value_t  *vv;
+    ngx_http_limit_req_ctx_t   *ctx;
+    ngx_http_limit_req_node_t  *lz;
+    ngx_http_limit_req_conf_t  *lzcf;
+
+    if (r->main->limit_req_set) {
+        return NGX_DECLINED;
+    }
+
+    lzcf = ngx_http_get_module_loc_conf(r, ngx_http_limit_req_module);
+
+    if (lzcf->shm_zone == NULL) {
+        return NGX_DECLINED;
+    }
+
+    ctx = lzcf->shm_zone->data;
+
+    vv = ngx_http_get_indexed_variable(r, ctx->index);
+
+    if (vv == NULL || vv->not_found) {
+        return NGX_DECLINED;
+    }
+
+    len = vv->len;
+
+    if (len == 0) {
+        return NGX_DECLINED;
+    }
+
+    if (len > 65535) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "the value of the \"%V\" variable "
+                      "is more than 65535 bytes: \"%v\"",
+                      &ctx->var, vv);
+        return NGX_DECLINED;
+    }
+
+    r->main->limit_req_set = 1;
+
+    hash = ngx_crc32_short(vv->data, len);
+
+    ngx_shmtx_lock(&ctx->shpool->mutex);
+
+    ngx_http_limit_req_expire(ctx, 1);
+
+    rc = ngx_http_limit_req_lookup(lzcf, hash, vv->data, len, &lz);
+
+    if (lz) {
+        ngx_queue_remove(&lz->queue);
+
+        ngx_queue_insert_head(ctx->queue, &lz->queue);
+
+        rate = lz->rate;
+
+    } else {
+        rate = 0.0;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                  "limit_req: %i %.3f", rc, rate);
+
+    if (rc == NGX_BUSY) {
+        ngx_shmtx_unlock(&ctx->shpool->mutex);
+
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "limiting requests, %.3f r/s", rate);
+
+        return NGX_HTTP_SERVICE_UNAVAILABLE;
+    }
+
+    if (rc == NGX_AGAIN) {
+        ngx_shmtx_unlock(&ctx->shpool->mutex);
+
+        if (lzcf->delay) {
+            ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
+                          "delaying requests, %.3f r/s", rate);
+
+            if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) {
+                return NGX_HTTP_INTERNAL_SERVER_ERROR;
+            }
+
+            r->read_event_handler = ngx_http_test_reading;
+            r->write_event_handler = ngx_http_limit_req_delay;
+            ngx_add_timer(r->connection->write, lzcf->delay);
+
+            return NGX_AGAIN;
+        }
+
+        return NGX_DECLINED;
+    }
+
+    if (rc == NGX_OK) {
+        goto done;
+    }
+
+    /* rc == NGX_DECLINED */
+
+    n = offsetof(ngx_rbtree_node_t, color)
+        + offsetof(ngx_http_limit_req_node_t, data)
+        + len;
+
+    node = ngx_slab_alloc_locked(ctx->shpool, n);
+    if (node == NULL) {
+
+        ngx_http_limit_req_expire(ctx, 0);
+
+        node = ngx_slab_alloc_locked(ctx->shpool, n);
+        if (node == NULL) {
+            ngx_shmtx_unlock(&ctx->shpool->mutex);
+            return NGX_HTTP_SERVICE_UNAVAILABLE;
+        }
+    }
+
+    lz = (ngx_http_limit_req_node_t *) &node->color;
+
+    node->key = hash;
+    lz->len = (u_char) len;
+
+    tp = ngx_timeofday();
+    lz->last = (ngx_msec_t) (tp->sec * 1000 + tp->msec);
+
+    lz->rate = 0.0;
+    ngx_memcpy(lz->data, vv->data, len);
+
+    ngx_rbtree_insert(ctx->rbtree, node);
+
+    ngx_queue_insert_head(ctx->queue, &lz->queue);
+
+done:
+
+    ngx_shmtx_unlock(&ctx->shpool->mutex);
+
+    return NGX_DECLINED;
+}
+
+
+static void
+ngx_http_limit_req_delay(ngx_http_request_t *r)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                  "limit_req delay");
+
+    if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) {
+        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    r->read_event_handler = ngx_http_block_reading;
+    r->write_event_handler = ngx_http_core_run_phases;
+
+    ngx_http_core_run_phases(r);
+}
+
+
+static void
+ngx_http_limit_req_rbtree_insert_value(ngx_rbtree_node_t *temp,
+    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
+{
+    ngx_rbtree_node_t          **p;
+    ngx_http_limit_req_node_t   *lzn, *lznt;
+
+    for ( ;; ) {
+
+        if (node->key < temp->key) {
+
+            p = &temp->left;
+
+        } else if (node->key > temp->key) {
+
+            p = &temp->right;
+
+        } else { /* node->key == temp->key */
+
+            lzn = (ngx_http_limit_req_node_t *) &node->color;
+            lznt = (ngx_http_limit_req_node_t *) &temp->color;
+
+            p = (ngx_memn2cmp(lzn->data, lznt->data, lzn->len, lznt->len) < 0)
+                ? &temp->left : &temp->right;
+        }
+
+        if (*p == sentinel) {
+            break;
+        }
+
+        temp = *p;
+    }
+
+    *p = node;
+    node->parent = temp;
+    node->left = sentinel;
+    node->right = sentinel;
+    ngx_rbt_red(node);
+}
+
+
+static ngx_int_t
+ngx_http_limit_req_lookup(ngx_http_limit_req_conf_t *lzcf, ngx_uint_t hash,
+    u_char *data, size_t len, ngx_http_limit_req_node_t **lzp)
+{
+    ngx_int_t                   rc;
+    ngx_time_t                 *tp;
+    ngx_msec_t                  now;
+    ngx_msec_int_t              ms;
+    ngx_rbtree_node_t          *node, *sentinel;
+    ngx_http_limit_req_ctx_t   *ctx;
+    ngx_http_limit_req_node_t  *lz;
+
+    ctx = lzcf->shm_zone->data;
+
+    node = ctx->rbtree->root;
+    sentinel = ctx->rbtree->sentinel;
+
+    while (node != sentinel) {
+
+        if (hash < node->key) {
+            node = node->left;
+            continue;
+        }
+
+        if (hash > node->key) {
+            node = node->right;
+            continue;
+        }
+
+        /* hash == node->key */
+
+        do {
+            lz = (ngx_http_limit_req_node_t *) &node->color;
+
+            rc = ngx_memn2cmp(data, lz->data, len, (size_t) lz->len);
+
+            if (rc == 0) {
+
+                tp = ngx_timeofday();
+
+                now = (ngx_msec_t) (tp->sec * 1000 + tp->msec);
+                ms = (ngx_msec_int_t) (now - lz->last);
+
+                lz->rate = lz->rate - ctx->rate * ngx_abs(ms) / 1000 + 1;
+
+                if (lz->rate < 0.0) {
+                    lz->rate = 0.0;
+                }
+
+                lz->last = now;
+
+                *lzp = lz;
+
+                if (lz->rate > lzcf->burst) {
+                    return NGX_BUSY;
+                }
+
+                if (lz->rate > ctx->rate) {
+                    return NGX_AGAIN;
+                }
+
+                return NGX_OK;
+            }
+
+            node = (rc < 0) ? node->left : node->right;
+
+        } while (node != sentinel && hash == node->key);
+
+        break;
+    }
+
+    *lzp = NULL;
+
+    return NGX_DECLINED;
+}
+
+
+static void
+ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx, ngx_uint_t n)
+{
+    float                       rate;
+    ngx_time_t                 *tp;
+    ngx_msec_t                  now;
+    ngx_queue_t                *q;
+    ngx_msec_int_t              ms;
+    ngx_rbtree_node_t          *node;
+    ngx_http_limit_req_node_t  *lz;
+
+    tp = ngx_timeofday();
+
+    now = (ngx_msec_t) (tp->sec * 1000 + tp->msec);
+
+    /*
+     * n == 1 deletes one or two zero rate entries
+     * n == 0 deletes oldest entry by force
+     *        and one or two zero rate entries
+     */
+
+    while (n < 3) {
+
+        if (ngx_queue_empty(ctx->queue)) {
+            return;
+        }
+
+        q = ngx_queue_last(ctx->queue);
+
+        lz = ngx_queue_data(q, ngx_http_limit_req_node_t, queue);
+
+        if (n++ != 0) {
+
+            ms = (ngx_msec_int_t) (now - lz->last);
+            ms = ngx_abs(ms);
+
+            if (ms < 60000) {
+                return;
+            }
+
+            rate = lz->rate - ctx->rate * ms / 1000;
+
+            if (rate > 0.0) {
+                return;
+            }
+        }
+
+        ngx_queue_remove(q);
+
+        node = (ngx_rbtree_node_t *)
+                   ((u_char *) lz - offsetof(ngx_rbtree_node_t, color));
+
+        ngx_rbtree_delete(ctx->rbtree, node);
+
+        ngx_slab_free_locked(ctx->shpool, node);
+    }
+}
+
+
+static ngx_int_t
+ngx_http_limit_req_init_zone(ngx_shm_zone_t *shm_zone, void *data)
+{
+    ngx_http_limit_req_ctx_t  *octx = data;
+
+    ngx_rbtree_node_t         *sentinel;
+    ngx_http_limit_req_ctx_t  *ctx;
+
+    ctx = shm_zone->data;
+
+    if (octx) {
+        if (ngx_strcmp(ctx->var.data, octx->var.data) != 0) {
+            ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
+                          "limit_req \"%V\" uses the \"%V\" variable "
+                          "while previously it used the \"%V\" variable",
+                          &shm_zone->name, &ctx->var, &octx->var);
+            return NGX_ERROR;
+        }
+
+        ctx->rbtree = octx->rbtree;
+        ctx->queue = octx->queue;
+        ctx->shpool = octx->shpool;
+
+        return NGX_OK;
+    }
+
+    ctx->shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+
+    ctx->rbtree = ngx_slab_alloc(ctx->shpool, sizeof(ngx_rbtree_t));
+    if (ctx->rbtree == NULL) {
+        return NGX_ERROR;
+    }
+
+    sentinel = ngx_slab_alloc(ctx->shpool, sizeof(ngx_rbtree_node_t));
+    if (sentinel == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_rbtree_init(ctx->rbtree, sentinel,
+                    ngx_http_limit_req_rbtree_insert_value);
+
+    ctx->queue = ngx_slab_alloc(ctx->shpool, sizeof(ngx_queue_t));
+    if (ctx->queue == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_queue_init(ctx->queue);
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_limit_req_create_conf(ngx_conf_t *cf)
+{
+    ngx_http_limit_req_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_req_conf_t));
+    if (conf == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->shm_zone = NULL;
+     *     conf->burst = 0.0;
+     *     conf->delay = 0;
+     */
+
+    return conf;
+}
+
+
+static char *
+ngx_http_limit_req_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_limit_req_conf_t *prev = parent;
+    ngx_http_limit_req_conf_t *conf = child;
+
+    if (conf->shm_zone == NULL) {
+        *conf = *prev;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_limit_req_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    u_char                    *p;
+    size_t                     size, len;
+    ngx_str_t                 *value, name, s;
+    ngx_int_t                  rate, scale;
+    ngx_uint_t                 i;
+    ngx_shm_zone_t            *shm_zone;
+    ngx_http_limit_req_ctx_t  *ctx;
+
+    value = cf->args->elts;
+
+    ctx = NULL;
+    size = 0;
+    rate = 1;
+    scale = 1;
+    name.len = 0;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+
+        if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {
+
+            name.data = value[i].data + 5;
+
+            p = (u_char *) ngx_strchr(name.data, ':');
+
+            if (p) {
+                name.len = p - name.data;
+
+                p++;
+
+                s.len = value[i].data + value[i].len - p;
+                s.data = p;
+
+                size = ngx_parse_size(&s);
+                if (size > 8191) {
+                    continue;
+                }
+            }
+
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid zone size \"%V\"", &value[i]);
+            return NGX_CONF_ERROR;
+        }
+
+        if (ngx_strncmp(value[i].data, "rate=", 5) == 0) {
+
+            len = value[i].len;
+            p = value[i].data + len - 3;
+
+            if (ngx_strncmp(p, "r/s", 3) == 0) {
+                scale = 1;
+                len -= 3;
+
+            } else if (ngx_strncmp(p, "r/m", 3) == 0) {
+                scale = 60;
+                len -= 3;
+            }
+
+            rate = ngx_atoi(value[i].data + 5, len - 5);
+            if (rate <= NGX_ERROR) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid rate \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (value[i].data[0] == '$') {
+
+            value[i].len--;
+            value[i].data++;
+
+            ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_req_ctx_t));
+            if (ctx == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            ctx->index = ngx_http_get_variable_index(cf, &value[i]);
+            if (ctx->index == NGX_ERROR) {
+                return NGX_CONF_ERROR;
+            }
+
+            ctx->var = value[i];
+
+            continue;
+        }
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid parameter \"%V\"", &value[i]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (name.len == 0 || size == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"%V\" must have \"zone\" parameter",
+                           &cmd->name);
+        return NGX_CONF_ERROR;
+    }
+
+    if (ctx == NULL) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "no variable is defined for limit_req_zone \"%V\"",
+                           &cmd->name);
+        return NGX_CONF_ERROR;
+    }
+
+    ctx->rate = (float) rate / scale;
+
+    shm_zone = ngx_shared_memory_add(cf, &name, size,
+                                     &ngx_http_limit_req_module);
+    if (shm_zone == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (shm_zone->data) {
+        ctx = shm_zone->data;
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                   "limit_req_zone \"%V\" is already bound to variable \"%V\"",
+                   &value[1], &ctx->var);
+        return NGX_CONF_ERROR;
+    }
+
+    shm_zone->init = ngx_http_limit_req_init_zone;
+    shm_zone->data = ctx;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_limit_req(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_limit_req_conf_t  *lzcf = conf;
+
+    u_char                    *p;
+    size_t                     len;
+    ngx_int_t                  burst, scale, delay;
+    ngx_str_t                 *value, s;
+    ngx_uint_t                 i;
+    ngx_http_limit_req_ctx_t  *ctx;
+
+    if (lzcf->shm_zone) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    burst = 0;
+    scale = 1;
+    delay = 0;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+
+        if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {
+
+            s.len = value[i].len - 5;
+            s.data = value[i].data + 5;
+
+            lzcf->shm_zone = ngx_shared_memory_add(cf, &s, 0,
+                                                   &ngx_http_limit_req_module);
+            if (lzcf->shm_zone == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "burst=", 6) == 0) {
+
+            len = value[i].len;
+            p = value[i].data + len - 3;
+
+            if (ngx_strncmp(p, "r/s", 3) == 0) {
+                scale = 1;
+                len -= 3;
+
+            } else if (ngx_strncmp(p, "r/m", 3) == 0) {
+                scale = 60;
+                len -= 3;
+            }
+
+            burst = ngx_atoi(value[i].data + 6, len - 6);
+            if (burst <= 0) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid burst rate \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "delay=", 6) == 0) {
+
+            s.len = value[i].len - 6;
+            s.data = value[i].data + 6;
+
+            delay = ngx_parse_time(&s, 0);
+            if (delay < 0) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                 "invalid clean_time value \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid parameter \"%V\"", &value[i]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (lzcf->shm_zone == NULL) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"%V\" must have \"zone\" parameter",
+                           &cmd->name);
+        return NGX_CONF_ERROR;
+    }
+
+    if (lzcf->shm_zone->data == NULL) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "unknown limit_req_zone \"%V\"",
+                           &lzcf->shm_zone->name);
+        return NGX_CONF_ERROR;
+    }
+
+    if (burst) {
+        lzcf->burst = (float) burst / scale;
+
+    } else {
+        ctx = lzcf->shm_zone->data;
+        lzcf->burst = ctx->rate;
+    }
+
+    lzcf->delay = (ngx_msec_t) delay;
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_limit_req_init(ngx_conf_t *cf)
+{
+    ngx_http_handler_pt        *h;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_limit_req_handler;
+
+    return NGX_OK;
+}
--- a/src/http/modules/ngx_http_log_module.c
+++ b/src/http/modules/ngx_http_log_module.c
@@ -433,7 +433,7 @@ ngx_http_log_script_write(ngx_http_reque
     of.log = 1;
     of.valid = llcf->open_file_cache_valid;
     of.min_uses = llcf->open_file_cache_min_uses;
-    of.directio = NGX_MAX_OFF_T_VALUE;
+    of.directio = NGX_OPEN_FILE_DIRECTIO_OFF;
 
     if (ngx_open_cached_file(llcf->open_file_cache, &log, &of, r->pool)
         != NGX_OK)
--- a/src/http/modules/ngx_http_proxy_module.c
+++ b/src/http/modules/ngx_http_proxy_module.c
@@ -536,10 +536,11 @@ static ngx_int_t
 ngx_http_proxy_eval(ngx_http_request_t *r, ngx_http_proxy_ctx_t *ctx,
     ngx_http_proxy_loc_conf_t *plcf)
 {
-    size_t     add;
-    u_short    port;
-    ngx_str_t  proxy;
-    ngx_url_t  u;
+    u_char     *p;
+    size_t      add;
+    u_short     port;
+    ngx_str_t   proxy;
+    ngx_url_t   u;
 
     if (ngx_http_script_run(r, &proxy, plcf->proxy_lengths->elts, 0,
                             plcf->proxy_values->elts)
@@ -589,6 +590,19 @@ ngx_http_proxy_eval(ngx_http_request_t *
         return NGX_ERROR;
     }
 
+    if (u.uri.len && u.uri.data[0] == '?') {
+        p = ngx_pnalloc(r->pool, u.uri.len + 1);
+        if (p == NULL) {
+            return NGX_ERROR;
+        }
+
+        *p++ = '/';
+        ngx_memcpy(p, u.uri.data, u.uri.len);
+
+        u.uri.len++;
+        u.uri.data = p - 1;
+    }
+
     if (ngx_http_proxy_set_vars(r->pool, &u, &ctx->vars) != NGX_OK) {
         return NGX_ERROR;
     }
@@ -602,7 +616,7 @@ ngx_http_proxy_eval(ngx_http_request_t *
     r->upstream->resolved->host = u.host;
     r->upstream->resolved->port = (in_port_t) (u.no_port ? u.default_port:
                                                            u.port);
-    r->upstream->resolved->default_port = u.default_port;
+    r->upstream->resolved->no_port = u.no_port;
 
     return NGX_OK;
 }
--- a/src/http/modules/ngx_http_secure_link_module.c
+++ b/src/http/modules/ngx_http_secure_link_module.c
@@ -113,7 +113,7 @@ url_start:
 
     len = last - p;
 
-    if (end - start != 32 || len < 3) {
+    if (end - start != 32 || len == 0) {
         goto not_found;
     }
 
--- a/src/http/modules/perl/nginx.pm
+++ b/src/http/modules/perl/nginx.pm
@@ -47,7 +47,7 @@ our @EXPORT = qw(
     HTTP_INSUFFICIENT_STORAGE
 );
 
-our $VERSION = '0.7.19';
+our $VERSION = '0.7.20';
 
 require XSLoader;
 XSLoader::load('nginx', $VERSION);
--- a/src/http/ngx_http.h
+++ b/src/http/ngx_http.h
@@ -106,6 +106,7 @@ size_t ngx_http_get_time(char *buf, time
 
 ngx_int_t ngx_http_discard_request_body(ngx_http_request_t *r);
 void ngx_http_block_reading(ngx_http_request_t *r);
+void ngx_http_test_reading(ngx_http_request_t *r);
 
 
 char *ngx_http_types_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
--- a/src/http/ngx_http_core_module.c
+++ b/src/http/ngx_http_core_module.c
@@ -1631,6 +1631,10 @@ ngx_http_server_addr(ngx_http_request_t 
         }
 
         r->in_addr = sin.sin_addr.s_addr;
+
+    } else {
+        sin.sin_family = c->sockaddr->sa_family;
+        sin.sin_addr.s_addr = r->in_addr;
     }
 
     if (s == NULL) {
@@ -3470,7 +3474,7 @@ ngx_http_core_directio(ngx_conf_t *cf, n
     value = cf->args->elts;
 
     if (ngx_strcmp(value[1].data, "off") == 0) {
-        clcf->directio = NGX_MAX_OFF_T_VALUE;
+        clcf->directio = NGX_OPEN_FILE_DIRECTIO_OFF;
         return NGX_CONF_OK;
     }
 
--- a/src/http/ngx_http_postpone_filter_module.c
+++ b/src/http/ngx_http_postpone_filter_module.c
@@ -216,7 +216,7 @@ ngx_http_postpone_filter_output_postpone
         r->postponed = r->postponed->next;
     }
 
-    if (r->out) {
+    if (r != r->main && r->out) {
         ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                        "http postpone filter out again \"%V?%V\"",
                        &r->uri, &r->args);
--- a/src/http/ngx_http_request.c
+++ b/src/http/ngx_http_request.c
@@ -39,7 +39,6 @@ static void ngx_http_request_handler(ngx
 static ngx_int_t ngx_http_set_write_handler(ngx_http_request_t *r);
 static void ngx_http_writer(ngx_http_request_t *r);
 
-static void ngx_http_test_reading(ngx_http_request_t *r);
 static void ngx_http_set_keepalive(ngx_http_request_t *r);
 static void ngx_http_keepalive_handler(ngx_event_t *ev);
 static void ngx_http_set_lingering_close(ngx_http_request_t *r);
@@ -2024,7 +2023,7 @@ ngx_http_block_reading(ngx_http_request_
 }
 
 
-static void
+void
 ngx_http_test_reading(ngx_http_request_t *r)
 {
     int                n;
--- a/src/http/ngx_http_request.h
+++ b/src/http/ngx_http_request.h
@@ -438,10 +438,12 @@ struct ngx_http_request_s {
     unsigned                          no_cache:1;
 
     /*
-     * instead of using the request context data in ngx_http_limit_zone_module
-     * we use the single bit in the request structure
+     * instead of using the request context data in
+     * ngx_http_limit_zone_module and ngx_http_limit_req_module 
+     * we use the single bits in the request structure
      */
     unsigned                          limit_zone_set:1;
+    unsigned                          limit_req_set:1;
 
 #if 0
     unsigned                          cacheable:1;
--- a/src/http/ngx_http_upstream.c
+++ b/src/http/ngx_http_upstream.c
@@ -405,7 +405,7 @@ ngx_http_upstream_init(ngx_http_request_
             uscf = uscfp[i];
 
             if (uscf->host.len == host->len
-                && ((uscf->port == 0 && u->resolved->default_port)
+                && ((uscf->port == 0 && u->resolved->no_port)
                      || uscf->port == u->resolved->port)
                 && ngx_memcmp(uscf->host.data, host->data, host->len) == 0)
             {
--- a/src/http/ngx_http_upstream.h
+++ b/src/http/ngx_http_upstream.h
@@ -209,7 +209,7 @@ typedef struct {
 typedef struct {
     ngx_str_t                       host;
     in_port_t                       port;
-    ngx_uint_t                      default_port; /* unsigned  default_port:1 */
+    ngx_uint_t                      no_port; /* unsigned  no_port:1 */
     ngx_uint_t                      naddrs;
     in_addr_t                      *addrs;
     ngx_resolver_ctx_t             *ctx;