# HG changeset patch
# User Valentin Bartenev <vbart@nginx.com>
# Date 1363008869 -14400
# Node ID c42b6ba37f33ea3f1ae2d1e2c67a3f7d5c0f3d52
# Parent  e0553ed7d9097bba14c70bbe33f847721fffbcfa
Create request object only after the first byte was received.

Previously, we always created an object and logged 400 (Bad Request)
in access log if a client closed connection without sending any data.
Such a connection was counted as "reading".

Since it's common for modern browsers to behave like this, it's no
longer considered an error if a client closes connection without
sending any data, and such a connection will be counted as "waiting".

Now, we do not log 400 (Bad Request) and keep memory footprint as
small as possible.

diff -r e0553ed7d909 -r c42b6ba37f33 src/http/ngx_http_request.c
--- a/src/http/ngx_http_request.c	Tue Mar 05 14:36:20 2013 +0000
+++ b/src/http/ngx_http_request.c	Mon Mar 11 17:34:29 2013 +0400
@@ -10,6 +10,7 @@
 #include <ngx_http.h>
 
 
+static void ngx_http_wait_request_handler(ngx_event_t *ev);
 static void ngx_http_init_request(ngx_event_t *ev);
 static void ngx_http_process_request_line(ngx_event_t *rev);
 static void ngx_http_process_request_headers(ngx_event_t *rev);
@@ -308,12 +309,12 @@ ngx_http_init_connection(ngx_connection_
     c->log->connection = c->number;
     c->log->handler = ngx_http_log_error;
     c->log->data = ctx;
-    c->log->action = "reading client request line";
+    c->log->action = "waiting for request";
 
     c->log_error = NGX_ERROR_INFO;
 
     rev = c->read;
-    rev->handler = ngx_http_init_request;
+    rev->handler = ngx_http_wait_request_handler;
     c->write->handler = ngx_http_empty_handler;
 
 #if (NGX_HTTP_SSL)
@@ -363,6 +364,99 @@ ngx_http_init_connection(ngx_connection_
 
 
 static void
+ngx_http_wait_request_handler(ngx_event_t *rev)
+{
+    size_t                     size;
+    ssize_t                    n;
+    ngx_buf_t                 *b;
+    ngx_connection_t          *c;
+    ngx_http_connection_t     *hc;
+    ngx_http_core_srv_conf_t  *cscf;
+
+    c = rev->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http wait request handler");
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    hc = c->data;
+    cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);
+
+    size = cscf->client_header_buffer_size;
+
+    b = c->buffer;
+
+    if (b == NULL) {
+        b = ngx_create_temp_buf(c->pool, size);
+        if (b == NULL) {
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        c->buffer = b;
+
+    } else if (b->start == NULL) {
+
+        b->start = ngx_palloc(c->pool, size);
+        if (b->start == NULL) {
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        b->pos = b->start;
+        b->last = b->start;
+        b->end = b->last + size;
+    }
+
+    n = c->recv(c, b->last, size);
+
+    if (n == NGX_AGAIN) {
+
+        if (!rev->timer_set) {
+            ngx_add_timer(rev, c->listening->post_accept_timeout);
+        }
+
+        if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        /*
+         * We are trying to not hold c->buffer's memory for an idle connection.
+         */
+
+        if (ngx_pfree(c->pool, b->start) == NGX_OK) {
+            b->start = NULL;
+        }
+
+        return;
+    }
+
+    if (n == NGX_ERROR) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    if (n == 0) {
+        ngx_log_error(NGX_LOG_INFO, c->log, ngx_socket_errno,
+                      "client closed connection");
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    b->last += n;
+
+    c->log->action = "reading client request line";
+
+    ngx_http_init_request(rev);
+}
+
+
+static void
 ngx_http_init_request(ngx_event_t *rev)
 {
     ngx_pool_t                 *pool;
@@ -377,13 +471,6 @@ ngx_http_init_request(ngx_event_t *rev)
 
     c = rev->data;
 
-    if (rev->timedout) {
-        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
-
-        ngx_http_close_connection(c);
-        return;
-    }
-
     c->requests++;
 
     hc = c->data;
@@ -425,16 +512,6 @@ ngx_http_init_request(ngx_event_t *rev)
 
     ngx_http_set_connection_log(r->connection, clcf->error_log);
 
-    if (c->buffer == NULL) {
-        c->buffer = ngx_create_temp_buf(c->pool,
-                                        cscf->client_header_buffer_size);
-        if (c->buffer == NULL) {
-            ngx_destroy_pool(r->pool);
-            ngx_http_close_connection(c);
-            return;
-        }
-    }
-
     r->header_in = hc->nbusy ? hc->busy[0] : c->buffer;
 
     if (ngx_list_init(&r->headers_out.headers, r->pool, 20,
@@ -592,10 +669,10 @@ ngx_http_ssl_handshake(ngx_event_t *rev)
 
         ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "plain http");
 
-        c->log->action = "reading client request line";
-
-        rev->handler = ngx_http_init_request;
-        ngx_http_init_request(rev);
+        c->log->action = "waiting for request";
+
+        rev->handler = ngx_http_wait_request_handler;
+        ngx_http_wait_request_handler(rev);
 
         return;
     }
@@ -620,12 +697,12 @@ ngx_http_ssl_handshake_handler(ngx_conne
 
         c->ssl->no_wait_shutdown = 1;
 
-        c->log->action = "reading client request line";
-
-        c->read->handler = ngx_http_init_request;
+        c->log->action = "waiting for request";
+
+        c->read->handler = ngx_http_wait_request_handler;
         /* STUB: epoll edge */ c->write->handler = ngx_http_empty_handler;
 
-        ngx_http_init_request(c->read);
+        ngx_http_wait_request_handler(c->read);
 
         return;
     }
# HG changeset patch
# User Valentin Bartenev <vbart@nginx.com>
# Date 1363008869 -14400
# Node ID 01c8251670d857920fbc25533f2670fd19bec1e3
# Parent  c42b6ba37f33ea3f1ae2d1e2c67a3f7d5c0f3d52
Use "client_header_timeout" for all requests in a connection.

Previously, only the first request in a connection used timeout
value from the "client_header_timeout" directive while reading
header.  All subsequent requests used "keepalive_timeout" for
that.

It happened because timeout of the read event was set to the
value of "keepalive_timeout" in ngx_http_set_keepalive(), but
was not removed when the next request arrived.

diff -r c42b6ba37f33 -r 01c8251670d8 src/http/ngx_http_request.c
--- a/src/http/ngx_http_request.c	Mon Mar 11 17:34:29 2013 +0400
+++ b/src/http/ngx_http_request.c	Mon Mar 11 17:34:29 2013 +0400
@@ -2736,8 +2736,6 @@ ngx_http_set_keepalive(ngx_http_request_
 
     c->data = hc;
 
-    ngx_add_timer(rev, clcf->keepalive_timeout);
-
     if (ngx_handle_read_event(rev, 0) != NGX_OK) {
         ngx_http_close_connection(c);
         return;
@@ -2753,6 +2751,10 @@ ngx_http_set_keepalive(ngx_http_request_
         hc->pipeline = 1;
         c->log->action = "reading client pipelined request line";
 
+        if (rev->timer_set) {
+            ngx_del_timer(rev);
+        }
+
         rev->handler = ngx_http_init_request;
         ngx_post_event(rev, &ngx_posted_events);
         return;
@@ -2872,6 +2874,8 @@ ngx_http_set_keepalive(ngx_http_request_
     c->idle = 1;
     ngx_reusable_connection(c, 1);
 
+    ngx_add_timer(rev, clcf->keepalive_timeout);
+
     if (rev->ready) {
         ngx_post_event(rev, &ngx_posted_events);
     }
@@ -2993,6 +2997,8 @@ ngx_http_keepalive_handler(ngx_event_t *
     c->idle = 0;
     ngx_reusable_connection(c, 0);
 
+    ngx_del_timer(rev);
+
     ngx_http_init_request(rev);
 }
 
# HG changeset patch
# User Valentin Bartenev <vbart@nginx.com>
# Date 1363008869 -14400
# Node ID 2dc69d57348d83e69e5af3b522dca4e5f5c79b09
# Parent  01c8251670d857920fbc25533f2670fd19bec1e3
Respect the new behavior of TCP_DEFER_ACCEPT.

In Linux 2.6.32, TCP_DEFER_ACCEPT was changed to accept connections
after the deferring period is finished without any data available.
(Reading from the socket returns EAGAIN in this case.)

Since in nginx TCP_DEFER_ACCEPT is set to "post_accept_timeout", we
do not need to wait longer if deferred accept returns with no data.

diff -r 01c8251670d8 -r 2dc69d57348d src/http/ngx_http_request.c
--- a/src/http/ngx_http_request.c	Mon Mar 11 17:34:29 2013 +0400
+++ b/src/http/ngx_http_request.c	Mon Mar 11 17:34:29 2013 +0400
@@ -416,6 +416,20 @@ ngx_http_wait_request_handler(ngx_event_
 
     if (n == NGX_AGAIN) {
 
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
+        if (c->listening->deferred_accept
+#if (NGX_HTTP_SSL)
+            && c->ssl == NULL
+#endif
+            )
+        {
+            ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
+                          "client timed out in deferred accept");
+            ngx_http_close_connection(c);
+            return;
+        }
+#endif
+
         if (!rev->timer_set) {
             ngx_add_timer(rev, c->listening->post_accept_timeout);
         }
@@ -617,6 +631,15 @@ ngx_http_ssl_handshake(ngx_event_t *rev)
     if (n == -1) {
         if (err == NGX_EAGAIN) {
 
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
+            if (c->listening->deferred_accept) {
+                ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
+                              "client timed out in deferred accept");
+                ngx_http_close_connection(c);
+                return;
+            }
+#endif
+
             if (!rev->timer_set) {
                 ngx_add_timer(rev, c->listening->post_accept_timeout);
             }
# HG changeset patch
# User Valentin V. Bartenev <ne@vbart.ru>
# Date 1351599261 -14400
# Node ID b44ddfee54dc44d3523f4f0be413291853094a1e
# Parent  2dc69d57348d83e69e5af3b522dca4e5f5c79b09
Removed c->single_connection flag.

The c->single_connection was intended to be used as lock mechanism
to serialize modifications of request object from several threads
working with client and upstream connections.  The flag is redundant
since threads in nginx have never been used that way.

diff -r 2dc69d57348d -r b44ddfee54dc src/core/ngx_connection.c
--- a/src/core/ngx_connection.c	Mon Mar 11 17:34:29 2013 +0400
+++ b/src/core/ngx_connection.c	Tue Oct 30 16:14:21 2012 +0400
@@ -900,11 +900,9 @@ ngx_close_connection(ngx_connection_t *c
     c->read->closed = 1;
     c->write->closed = 1;
 
-    if (c->single_connection) {
-        ngx_unlock(&c->lock);
-        c->read->locked = 0;
-        c->write->locked = 0;
-    }
+    ngx_unlock(&c->lock);
+    c->read->locked = 0;
+    c->write->locked = 0;
 
     ngx_mutex_unlock(ngx_posted_events_mutex);
 
diff -r 2dc69d57348d -r b44ddfee54dc src/core/ngx_connection.h
--- a/src/core/ngx_connection.h	Mon Mar 11 17:34:29 2013 +0400
+++ b/src/core/ngx_connection.h	Tue Oct 30 16:14:21 2012 +0400
@@ -152,7 +152,6 @@ struct ngx_connection_s {
 
     unsigned            log_error:3;     /* ngx_connection_log_error_e */
 
-    unsigned            single_connection:1;
     unsigned            unexpected_eof:1;
     unsigned            timedout:1;
     unsigned            error:1;
diff -r 2dc69d57348d -r b44ddfee54dc src/http/ngx_http_request.c
--- a/src/http/ngx_http_request.c	Mon Mar 11 17:34:29 2013 +0400
+++ b/src/http/ngx_http_request.c	Tue Oct 30 16:14:21 2012 +0400
@@ -554,7 +554,6 @@ ngx_http_init_request(ngx_event_t *rev)
         return;
     }
 
-    c->single_connection = 1;
     c->destroyed = 0;
 
 #if (NGX_HTTP_SSL)
diff -r 2dc69d57348d -r b44ddfee54dc src/http/ngx_http_upstream.c
--- a/src/http/ngx_http_upstream.c	Mon Mar 11 17:34:29 2013 +0400
+++ b/src/http/ngx_http_upstream.c	Tue Oct 30 16:14:21 2012 +0400
@@ -1118,8 +1118,6 @@ ngx_http_upstream_connect(ngx_http_reque
 
     r->connection->log->action = "connecting to upstream";
 
-    r->connection->single_connection = 0;
-
     if (u->state && u->state->response_sec) {
         tp = ngx_timeofday();
         u->state->response_sec = tp->sec - u->state->response_sec;
# HG changeset patch
# User Valentin Bartenev <vbart@nginx.com>
# Date 1363008869 -14400
# Node ID a8e12e0381b434b2c1f0ec8ddee42e4b87ae64e9
# Parent  b44ddfee54dc44d3523f4f0be413291853094a1e
Refactored ngx_http_init_request().

Now it can be used as the request object factory with minimal impact on the
connection object.  Therefore it was renamed to ngx_http_create_request().

diff -r b44ddfee54dc -r a8e12e0381b4 src/http/ngx_http_request.c
--- a/src/http/ngx_http_request.c	Tue Oct 30 16:14:21 2012 +0400
+++ b/src/http/ngx_http_request.c	Mon Mar 11 17:34:29 2013 +0400
@@ -11,7 +11,7 @@
 
 
 static void ngx_http_wait_request_handler(ngx_event_t *ev);
-static void ngx_http_init_request(ngx_event_t *ev);
+static ngx_http_request_t *ngx_http_create_request(ngx_connection_t *c);
 static void ngx_http_process_request_line(ngx_event_t *rev);
 static void ngx_http_process_request_headers(ngx_event_t *rev);
 static ssize_t ngx_http_read_request_header(ngx_http_request_t *r);
@@ -466,16 +466,22 @@ ngx_http_wait_request_handler(ngx_event_
 
     c->log->action = "reading client request line";
 
-    ngx_http_init_request(rev);
+    c->data = ngx_http_create_request(c);
+    if (c->data == NULL) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    rev->handler = ngx_http_process_request_line;
+    ngx_http_process_request_line(rev);
 }
 
 
-static void
-ngx_http_init_request(ngx_event_t *rev)
+static ngx_http_request_t *
+ngx_http_create_request(ngx_connection_t *c)
 {
     ngx_pool_t                 *pool;
     ngx_time_t                 *tp;
-    ngx_connection_t           *c;
     ngx_http_request_t         *r;
     ngx_http_log_ctx_t         *ctx;
     ngx_http_connection_t      *hc;
@@ -483,8 +489,6 @@ ngx_http_init_request(ngx_event_t *rev)
     ngx_http_core_loc_conf_t   *clcf;
     ngx_http_core_main_conf_t  *cmcf;
 
-    c = rev->data;
-
     c->requests++;
 
     hc = c->data;
@@ -493,27 +497,19 @@ ngx_http_init_request(ngx_event_t *rev)
 
     pool = ngx_create_pool(cscf->request_pool_size, c->log);
     if (pool == NULL) {
-        ngx_http_close_connection(c);
-        return;
+        return NULL;
     }
 
     r = ngx_pcalloc(pool, sizeof(ngx_http_request_t));
     if (r == NULL) {
         ngx_destroy_pool(pool);
-        ngx_http_close_connection(c);
-        return;
+        return NULL;
     }
 
     r->pool = pool;
 
-    r->pipeline = hc->pipeline;
-
-    c->data = r;
     r->http_connection = hc;
-
-    c->sent = 0;
     r->signature = NGX_HTTP_MODULE;
-
     r->connection = c;
 
     r->main_conf = hc->conf_ctx->main_conf;
@@ -533,15 +529,13 @@ ngx_http_init_request(ngx_event_t *rev)
         != NGX_OK)
     {
         ngx_destroy_pool(r->pool);
-        ngx_http_close_connection(c);
-        return;
+        return NULL;
     }
 
     r->ctx = ngx_pcalloc(r->pool, sizeof(void *) * ngx_http_max_module);
     if (r->ctx == NULL) {
         ngx_destroy_pool(r->pool);
-        ngx_http_close_connection(c);
-        return;
+        return NULL;
     }
 
     cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
@@ -550,12 +544,9 @@ ngx_http_init_request(ngx_event_t *rev)
                                         * sizeof(ngx_http_variable_value_t));
     if (r->variables == NULL) {
         ngx_destroy_pool(r->pool);
-        ngx_http_close_connection(c);
-        return;
+        return NULL;
     }
 
-    c->destroyed = 0;
-
 #if (NGX_HTTP_SSL)
     if (c->ssl) {
         r->main_filter_need_in_memory = 1;
@@ -592,8 +583,7 @@ ngx_http_init_request(ngx_event_t *rev)
     (void) ngx_atomic_fetch_add(ngx_stat_requests, 1);
 #endif
 
-    rev->handler = ngx_http_process_request_line;
-    ngx_http_process_request_line(rev);
+    return r;
 }
 
 
@@ -2722,7 +2712,7 @@ ngx_http_set_keepalive(ngx_http_request_
             /*
              * If the large header buffers were allocated while the previous
              * request processing then we do not use c->buffer for
-             * the pipelined request (see ngx_http_init_request()).
+             * the pipelined request (see ngx_http_create_request()).
              *
              * Now we would move the large header buffers to the free list.
              */
@@ -2770,20 +2760,30 @@ ngx_http_set_keepalive(ngx_http_request_
 
         ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "pipelined request");
 
-        hc->pipeline = 1;
         c->log->action = "reading client pipelined request line";
 
+        r = ngx_http_create_request(c);
+        if (r == NULL) {
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        r->pipeline = 1;
+
+        c->data = r;
+
+        c->sent = 0;
+        c->destroyed = 0;
+
         if (rev->timer_set) {
             ngx_del_timer(rev);
         }
 
-        rev->handler = ngx_http_init_request;
+        rev->handler = ngx_http_process_request_line;
         ngx_post_event(rev, &ngx_posted_events);
         return;
     }
 
-    hc->pipeline = 0;
-
     /*
      * To keep a memory footprint as small as possible for an idle keepalive
      * connection we try to free c->buffer's memory if it was allocated outside
@@ -3019,9 +3019,19 @@ ngx_http_keepalive_handler(ngx_event_t *
     c->idle = 0;
     ngx_reusable_connection(c, 0);
 
+    c->data = ngx_http_create_request(c);
+    if (c->data == NULL) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    c->sent = 0;
+    c->destroyed = 0;
+
     ngx_del_timer(rev);
 
-    ngx_http_init_request(rev);
+    rev->handler = ngx_http_process_request_line;
+    ngx_http_process_request_line(rev);
 }
 
 
diff -r b44ddfee54dc -r a8e12e0381b4 src/http/ngx_http_request.h
--- a/src/http/ngx_http_request.h	Tue Oct 30 16:14:21 2012 +0400
+++ b/src/http/ngx_http_request.h	Mon Mar 11 17:34:29 2013 +0400
@@ -308,8 +308,9 @@ typedef struct {
     ngx_buf_t                       **free;
     ngx_int_t                         nfree;
 
-    unsigned                          pipeline:1;
-    unsigned                          ssl:1;
+#if (NGX_HTTP_SSL)
+    ngx_uint_t                        ssl;    /* unsigned  ssl:1; */
+#endif
 } ngx_http_connection_t;
 
 
# HG changeset patch
# User Valentin Bartenev <vbart@nginx.com>
# Date 1363008869 -14400
# Node ID dda29f9a2b2a975538da2a2d7870cb0e8a408b80
# Parent  a8e12e0381b434b2c1f0ec8ddee42e4b87ae64e9
SSL: Next Protocol Negotiation extension support.

Not only this is useful for the upcoming SPDY support, but it can
also help to improve HTTPS performance by enabling TLS False Start
in Chrome/Chromium browsers [1].  So, we always enable NPN for HTTPS
if it is supported by OpenSSL.

[1] http://www.imperialviolet.org/2012/04/11/falsestart.html

diff -r a8e12e0381b4 -r dda29f9a2b2a src/http/modules/ngx_http_ssl_module.c
--- a/src/http/modules/ngx_http_ssl_module.c	Mon Mar 11 17:34:29 2013 +0400
+++ b/src/http/modules/ngx_http_ssl_module.c	Mon Mar 11 17:34:29 2013 +0400
@@ -18,6 +18,11 @@ typedef ngx_int_t (*ngx_ssl_variable_han
 #define NGX_DEFAULT_ECDH_CURVE  "prime256v1"
 
 
+#ifdef TLSEXT_TYPE_next_proto_neg
+static int ngx_http_ssl_npn_advertised(ngx_ssl_conn_t *ssl_conn,
+    const unsigned char **out, unsigned int *outlen, void *arg);
+#endif
+
 static ngx_int_t ngx_http_ssl_static_variable(ngx_http_request_t *r,
     ngx_http_variable_value_t *v, uintptr_t data);
 static ngx_int_t ngx_http_ssl_variable(ngx_http_request_t *r,
@@ -262,6 +267,30 @@ static ngx_http_variable_t  ngx_http_ssl
 static ngx_str_t ngx_http_ssl_sess_id_ctx = ngx_string("HTTP");
 
 
+#ifdef TLSEXT_TYPE_next_proto_neg
+
+#define NGX_HTTP_NPN_ADVERTISE  "\x08http/1.1"
+
+static int
+ngx_http_ssl_npn_advertised(ngx_ssl_conn_t *ssl_conn,
+    const unsigned char **out, unsigned int *outlen, void *arg)
+{
+#if (NGX_DEBUG)
+    ngx_connection_t  *c;
+
+    c = ngx_ssl_get_connection(ssl_conn);
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "SSL NPN advertised");
+#endif
+
+    *out = (unsigned char *) NGX_HTTP_NPN_ADVERTISE;
+    *outlen = sizeof(NGX_HTTP_NPN_ADVERTISE) - 1;
+
+    return SSL_TLSEXT_ERR_OK;
+}
+
+#endif
+
+
 static ngx_int_t
 ngx_http_ssl_static_variable(ngx_http_request_t *r,
     ngx_http_variable_value_t *v, uintptr_t data)
@@ -490,6 +519,11 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *
 
 #endif
 
+#ifdef TLSEXT_TYPE_next_proto_neg
+    SSL_CTX_set_next_protos_advertised_cb(conf->ssl.ctx,
+                                          ngx_http_ssl_npn_advertised, NULL);
+#endif
+
     cln = ngx_pool_cleanup_add(cf->pool, 0);
     if (cln == NULL) {
         return NGX_CONF_ERROR;
# HG changeset patch
# User Valentin Bartenev <vbart@nginx.com>
# Date 1363008869 -14400
# Node ID 0b15bb8b69dfe7c02ab4d56e3be57b8f151481a7
# Parent  dda29f9a2b2a975538da2a2d7870cb0e8a408b80
Gzip: fixed setting of NGX_HTTP_GZIP_BUFFERED.

In r2411 setting of NGX_HTTP_GZIP_BUFFERED in c->buffered was moved from
ngx_http_gzip_filter_deflate_start() to ngx_http_gzip_filter_buffer() since
it was always called first.  But in r2543 the "postpone_gzipping" directive
was introduced, and if postponed gzipping is disabled (the default setting),
ngx_http_gzip_filter_buffer() is not called at all.

We must always set NGX_HTTP_GZIP_BUFFERED after the start of compression
since there is always a trailer that is buffered.

There are no known cases when it leads to any problem with current code.
But we already had troubles in upcoming SPDY implementation.

diff -r dda29f9a2b2a -r 0b15bb8b69df src/http/modules/ngx_http_gzip_filter_module.c
--- a/src/http/modules/ngx_http_gzip_filter_module.c	Mon Mar 11 17:34:29 2013 +0400
+++ b/src/http/modules/ngx_http_gzip_filter_module.c	Mon Mar 11 17:34:29 2013 +0400
@@ -620,6 +620,8 @@ ngx_http_gzip_filter_deflate_start(ngx_h
         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;
# HG changeset patch
# User Valentin Bartenev <vbart@nginx.com>
# Date 1363010134 -14400
# Node ID b8702a990706f5e4f99f9ffc27aef082bcf15cb4
# Parent  0b15bb8b69dfe7c02ab4d56e3be57b8f151481a7
Removed unused prototype of ngx_http_find_server_conf().

This function prototype and its implementation was added in r90,
but the implementation was removed in r97.

diff -r 0b15bb8b69df -r b8702a990706 src/http/ngx_http.h
--- a/src/http/ngx_http.h	Mon Mar 11 17:34:29 2013 +0400
+++ b/src/http/ngx_http.h	Mon Mar 11 17:55:34 2013 +0400
@@ -104,7 +104,6 @@ ngx_int_t ngx_http_parse_chunked(ngx_htt
     ngx_http_chunked_t *ctx);
 
 
-ngx_int_t ngx_http_find_server_conf(ngx_http_request_t *r);
 void ngx_http_update_location_config(ngx_http_request_t *r);
 void ngx_http_handler(ngx_http_request_t *r);
 void ngx_http_run_posted_requests(ngx_connection_t *c);
# HG changeset patch
# User Valentin Bartenev <vbart@nginx.com>
# Date 1363011730 -14400
# Node ID 8be96294e4defb89b36256f8b22ef09eb603dea6
# Parent  b8702a990706f5e4f99f9ffc27aef082bcf15cb4
Allow to reuse connections that wait their first request.

This should improve behavior under deficiency of connections.

diff -r b8702a990706 -r 8be96294e4de src/http/ngx_http_request.c
--- a/src/http/ngx_http_request.c	Mon Mar 11 17:55:34 2013 +0400
+++ b/src/http/ngx_http_request.c	Mon Mar 11 18:22:10 2013 +0400
@@ -354,6 +354,8 @@ ngx_http_init_connection(ngx_connection_
         return;
     }
 
+    ngx_reusable_connection(c, 1);
+
     ngx_add_timer(rev, c->listening->post_accept_timeout);
 
     if (ngx_handle_read_event(rev, 0) != NGX_OK) {
@@ -383,6 +385,11 @@ ngx_http_wait_request_handler(ngx_event_
         return;
     }
 
+    if (c->close) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
     hc = c->data;
     cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);
 
@@ -432,6 +439,7 @@ ngx_http_wait_request_handler(ngx_event_
 
         if (!rev->timer_set) {
             ngx_add_timer(rev, c->listening->post_accept_timeout);
+            ngx_reusable_connection(c, 1);
         }
 
         if (ngx_handle_read_event(rev, 0) != NGX_OK) {
@@ -466,6 +474,8 @@ ngx_http_wait_request_handler(ngx_event_
 
     c->log->action = "reading client request line";
 
+    ngx_reusable_connection(c, 0);
+
     c->data = ngx_http_create_request(c);
     if (c->data == NULL) {
         ngx_http_close_connection(c);
@@ -611,6 +621,11 @@ ngx_http_ssl_handshake(ngx_event_t *rev)
         return;
     }
 
+    if (c->close) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
     n = recv(c->fd, (char *) buf, 1, MSG_PEEK);
 
     err = ngx_socket_errno;
@@ -670,6 +685,8 @@ ngx_http_ssl_handshake(ngx_event_t *rev)
                     ngx_add_timer(rev, c->listening->post_accept_timeout);
                 }
 
+                ngx_reusable_connection(c, 0);
+
                 c->ssl->handler = ngx_http_ssl_handshake_handler;
                 return;
             }
@@ -714,6 +731,8 @@ ngx_http_ssl_handshake_handler(ngx_conne
         c->read->handler = ngx_http_wait_request_handler;
         /* STUB: epoll edge */ c->write->handler = ngx_http_empty_handler;
 
+        ngx_reusable_connection(c, 1);
+
         ngx_http_wait_request_handler(c->read);
 
         return;
# HG changeset patch
# User Valentin Bartenev <vbart@nginx.com>
# Date 1363011731 -14400
# Node ID 7ecddb37e4c85bf8272f517b22e30dec83d662cf
# Parent  8be96294e4defb89b36256f8b22ef09eb603dea6
Status: introduced the "ngx_stat_waiting" counter.

And corresponding variable $connections_waiting was added.

Previously, waiting connections were counted as the difference between
active connections and the sum of reading and writing connections.
That made it impossible to count more than one request in one connection
as reading or writing (as is the case for SPDY).

Also, we no longer count connections in handshake state as waiting.

diff -r 8be96294e4de -r 7ecddb37e4c8 src/core/ngx_connection.c
--- a/src/core/ngx_connection.c	Mon Mar 11 18:22:10 2013 +0400
+++ b/src/core/ngx_connection.c	Mon Mar 11 18:22:11 2013 +0400
@@ -970,6 +970,10 @@ ngx_reusable_connection(ngx_connection_t
 
     if (c->reusable) {
         ngx_queue_remove(&c->queue);
+
+#if (NGX_STAT_STUB)
+        (void) ngx_atomic_fetch_add(ngx_stat_waiting, -1);
+#endif
     }
 
     c->reusable = reusable;
@@ -979,6 +983,10 @@ ngx_reusable_connection(ngx_connection_t
 
         ngx_queue_insert_head(
             (ngx_queue_t *) &ngx_cycle->reusable_connections_queue, &c->queue);
+
+#if (NGX_STAT_STUB)
+        (void) ngx_atomic_fetch_add(ngx_stat_waiting, 1);
+#endif
     }
 }
 
diff -r 8be96294e4de -r 7ecddb37e4c8 src/event/ngx_event.c
--- a/src/event/ngx_event.c	Mon Mar 11 18:22:10 2013 +0400
+++ b/src/event/ngx_event.c	Mon Mar 11 18:22:11 2013 +0400
@@ -73,6 +73,8 @@ ngx_atomic_t   ngx_stat_reading0;
 ngx_atomic_t  *ngx_stat_reading = &ngx_stat_reading0;
 ngx_atomic_t   ngx_stat_writing0;
 ngx_atomic_t  *ngx_stat_writing = &ngx_stat_writing0;
+ngx_atomic_t   ngx_stat_waiting0;
+ngx_atomic_t  *ngx_stat_waiting = &ngx_stat_waiting0;
 
 #endif
 
@@ -511,7 +513,8 @@ ngx_event_module_init(ngx_cycle_t *cycle
            + cl          /* ngx_stat_requests */
            + cl          /* ngx_stat_active */
            + cl          /* ngx_stat_reading */
-           + cl;         /* ngx_stat_writing */
+           + cl          /* ngx_stat_writing */
+           + cl;         /* ngx_stat_waiting */
 
 #endif
 
@@ -558,6 +561,7 @@ ngx_event_module_init(ngx_cycle_t *cycle
     ngx_stat_active = (ngx_atomic_t *) (shared + 6 * cl);
     ngx_stat_reading = (ngx_atomic_t *) (shared + 7 * cl);
     ngx_stat_writing = (ngx_atomic_t *) (shared + 8 * cl);
+    ngx_stat_waiting = (ngx_atomic_t *) (shared + 9 * cl);
 
 #endif
 
diff -r 8be96294e4de -r 7ecddb37e4c8 src/event/ngx_event.h
--- a/src/event/ngx_event.h	Mon Mar 11 18:22:10 2013 +0400
+++ b/src/event/ngx_event.h	Mon Mar 11 18:22:11 2013 +0400
@@ -511,6 +511,7 @@ extern ngx_atomic_t  *ngx_stat_requests;
 extern ngx_atomic_t  *ngx_stat_active;
 extern ngx_atomic_t  *ngx_stat_reading;
 extern ngx_atomic_t  *ngx_stat_writing;
+extern ngx_atomic_t  *ngx_stat_waiting;
 
 #endif
 
diff -r 8be96294e4de -r 7ecddb37e4c8 src/http/modules/ngx_http_stub_status_module.c
--- a/src/http/modules/ngx_http_stub_status_module.c	Mon Mar 11 18:22:10 2013 +0400
+++ b/src/http/modules/ngx_http_stub_status_module.c	Mon Mar 11 18:22:11 2013 +0400
@@ -73,6 +73,9 @@ static ngx_http_variable_t  ngx_http_stu
     { ngx_string("connections_writing"), NULL, ngx_http_stub_status_variable,
       2, NGX_HTTP_VAR_NOCACHEABLE, 0 },
 
+    { ngx_string("connections_waiting"), NULL, ngx_http_stub_status_variable,
+      3, NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
     { ngx_null_string, NULL, NULL, 0, 0, 0 }
 };
 
@@ -83,7 +86,7 @@ static ngx_int_t ngx_http_status_handler
     ngx_int_t          rc;
     ngx_buf_t         *b;
     ngx_chain_t        out;
-    ngx_atomic_int_t   ap, hn, ac, rq, rd, wr;
+    ngx_atomic_int_t   ap, hn, ac, rq, rd, wr, wa;
 
     if (r->method != NGX_HTTP_GET && r->method != NGX_HTTP_HEAD) {
         return NGX_HTTP_NOT_ALLOWED;
@@ -126,6 +129,7 @@ static ngx_int_t ngx_http_status_handler
     rq = *ngx_stat_requests;
     rd = *ngx_stat_reading;
     wr = *ngx_stat_writing;
+    wa = *ngx_stat_waiting;
 
     b->last = ngx_sprintf(b->last, "Active connections: %uA \n", ac);
 
@@ -135,7 +139,7 @@ static ngx_int_t ngx_http_status_handler
     b->last = ngx_sprintf(b->last, " %uA %uA %uA \n", ap, hn, rq);
 
     b->last = ngx_sprintf(b->last, "Reading: %uA Writing: %uA Waiting: %uA \n",
-                          rd, wr, ac - (rd + wr));
+                          rd, wr, wa);
 
     r->headers_out.status = NGX_HTTP_OK;
     r->headers_out.content_length_n = b->last - b->pos;
@@ -177,6 +181,10 @@ ngx_http_stub_status_variable(ngx_http_r
         value = *ngx_stat_writing;
         break;
 
+    case 3:
+        value = *ngx_stat_waiting;
+        break;
+
     /* suppress warning */
     default:
         value = 0;
# HG changeset patch
# User Valentin Bartenev <vbart@nginx.com>
# Date 1363129094 -14400
# Node ID 0fed3becc0569fca16a87685f56adc6a3c2c7964
# Parent  7ecddb37e4c85bf8272f517b22e30dec83d662cf
Preliminary experimental support for SPDY draft 2.

diff -r 7ecddb37e4c8 -r 0fed3becc056 auto/modules
--- a/auto/modules	Mon Mar 11 18:22:11 2013 +0400
+++ b/auto/modules	Wed Mar 13 02:58:14 2013 +0400
@@ -118,8 +118,13 @@ fi
 
 HTTP_FILTER_MODULES="$HTTP_WRITE_FILTER_MODULE \
                      $HTTP_HEADER_FILTER_MODULE \
-                     $HTTP_CHUNKED_FILTER_MODULE \
-                     $HTTP_RANGE_HEADER_FILTER_MODULE"
+                     $HTTP_CHUNKED_FILTER_MODULE";
+
+if [ $HTTP_SPDY = YES ]; then
+    HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES $HTTP_SPDY_FILTER_MODULE";
+fi
+
+HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES $HTTP_RANGE_HEADER_FILTER_MODULE";
 
 if [ $HTTP_GZIP = YES ]; then
     have=NGX_HTTP_GZIP . auto/have
@@ -179,6 +184,15 @@ if [ $HTTP_USERID = YES ]; then
     HTTP_SRCS="$HTTP_SRCS $HTTP_USERID_SRCS"
 fi
 
+
+if [ $HTTP_SPDY = YES ]; then
+    have=NGX_HTTP_SPDY . auto/have
+    USE_ZLIB=YES
+    HTTP_MODULES="$HTTP_MODULES $HTTP_SPDY_MODULE"
+    HTTP_DEPS="$HTTP_DEPS $HTTP_SPDY_DEPS"
+    HTTP_SRCS="$HTTP_SRCS $HTTP_SPDY_SRCS"
+fi
+
 HTTP_MODULES="$HTTP_MODULES $HTTP_STATIC_MODULE"
 
 if [ $HTTP_GZIP_STATIC = YES ]; then
diff -r 7ecddb37e4c8 -r 0fed3becc056 auto/options
--- a/auto/options	Mon Mar 11 18:22:11 2013 +0400
+++ b/auto/options	Wed Mar 13 02:58:14 2013 +0400
@@ -60,6 +60,7 @@ HTTP_CACHE=YES
 HTTP_CHARSET=YES
 HTTP_GZIP=YES
 HTTP_SSL=NO
+HTTP_SPDY=NO
 HTTP_SSI=YES
 HTTP_POSTPONE=NO
 HTTP_REALIP=NO
@@ -202,6 +203,7 @@ do
         --http-scgi-temp-path=*)         NGX_HTTP_SCGI_TEMP_PATH="$value" ;;
 
         --with-http_ssl_module)          HTTP_SSL=YES               ;;
+        --with-http_spdy_module)         HTTP_SPDY=YES              ;;
         --with-http_realip_module)       HTTP_REALIP=YES            ;;
         --with-http_addition_module)     HTTP_ADDITION=YES          ;;
         --with-http_xslt_module)         HTTP_XSLT=YES              ;;
diff -r 7ecddb37e4c8 -r 0fed3becc056 auto/sources
--- a/auto/sources	Mon Mar 11 18:22:11 2013 +0400
+++ b/auto/sources	Wed Mar 13 02:58:14 2013 +0400
@@ -324,6 +324,16 @@ HTTP_POSTPONE_FILTER_SRCS=src/http/ngx_h
 HTTP_FILE_CACHE_SRCS=src/http/ngx_http_file_cache.c
 
 
+HTTP_SPDY_MODULE=ngx_http_spdy_module
+HTTP_SPDY_DEPS="src/http/ngx_http_spdy.h \
+                src/http/ngx_http_spdy_module.h"
+HTTP_SPDY_SRCS="src/http/ngx_http_spdy.c \
+                src/http/ngx_http_spdy_module.c \
+                src/http/ngx_http_spdy_filter_module.c"
+
+HTTP_SPDY_FILTER_MODULE="ngx_http_spdy_filter_module"
+
+
 HTTP_CHARSET_FILTER_MODULE=ngx_http_charset_filter_module
 HTTP_CHARSET_SRCS=src/http/modules/ngx_http_charset_filter_module.c
 
diff -r 7ecddb37e4c8 -r 0fed3becc056 src/http/modules/ngx_http_ssl_module.c
--- a/src/http/modules/ngx_http_ssl_module.c	Mon Mar 11 18:22:11 2013 +0400
+++ b/src/http/modules/ngx_http_ssl_module.c	Wed Mar 13 02:58:14 2013 +0400
@@ -275,11 +275,27 @@ static int
 ngx_http_ssl_npn_advertised(ngx_ssl_conn_t *ssl_conn,
     const unsigned char **out, unsigned int *outlen, void *arg)
 {
-#if (NGX_DEBUG)
+#if (NGX_HTTP_SPDY || NGX_DEBUG)
     ngx_connection_t  *c;
 
     c = ngx_ssl_get_connection(ssl_conn);
+#endif
+
     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "SSL NPN advertised");
+
+#if (NGX_HTTP_SPDY)
+    {
+    ngx_http_connection_t  *hc;
+
+    hc = c->data;
+
+    if (hc->addr_conf->spdy) {
+        *out = (unsigned char *) NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE;
+        *outlen = sizeof(NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE) - 1;
+
+        return SSL_TLSEXT_ERR_OK;
+    }
+    }
 #endif
 
     *out = (unsigned char *) NGX_HTTP_NPN_ADVERTISE;
diff -r 7ecddb37e4c8 -r 0fed3becc056 src/http/ngx_http.c
--- a/src/http/ngx_http.c	Mon Mar 11 18:22:11 2013 +0400
+++ b/src/http/ngx_http.c	Wed Mar 13 02:58:14 2013 +0400
@@ -1225,6 +1225,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, n
 #if (NGX_HTTP_SSL)
     ngx_uint_t             ssl;
 #endif
+#if (NGX_HTTP_SPDY)
+    ngx_uint_t             spdy;
+#endif
 
     /*
      * we cannot compare whole sockaddr struct's as kernel
@@ -1277,6 +1280,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, n
 #if (NGX_HTTP_SSL)
         ssl = lsopt->ssl || addr[i].opt.ssl;
 #endif
+#if (NGX_HTTP_SPDY)
+        spdy = lsopt->spdy || addr[i].opt.spdy;
+#endif
 
         if (lsopt->set) {
 
@@ -1307,6 +1313,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, n
 #if (NGX_HTTP_SSL)
         addr[i].opt.ssl = ssl;
 #endif
+#if (NGX_HTTP_SPDY)
+        addr[i].opt.spdy = spdy;
+#endif
 
         return NGX_OK;
     }
@@ -1337,6 +1346,14 @@ ngx_http_add_address(ngx_conf_t *cf, ngx
         }
     }
 
+#if (NGX_HTTP_SPDY && NGX_HTTP_SSL && !defined TLSEXT_TYPE_next_proto_neg)
+    if (lsopt->spdy && lsopt->ssl) {
+        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                           "nginx was built without OpenSSL NPN support, "
+                           "SPDY is not enabled for %s", lsopt->addr);
+    }
+#endif
+
     addr = ngx_array_push(&port->addrs);
     if (addr == NULL) {
         return NGX_ERROR;
@@ -1820,6 +1837,9 @@ ngx_http_add_addrs(ngx_conf_t *cf, ngx_h
 #if (NGX_HTTP_SSL)
         addrs[i].conf.ssl = addr[i].opt.ssl;
 #endif
+#if (NGX_HTTP_SPDY)
+        addrs[i].conf.spdy = addr[i].opt.spdy;
+#endif
 
         if (addr[i].hash.buckets == NULL
             && (addr[i].wc_head == NULL
@@ -1881,6 +1901,9 @@ ngx_http_add_addrs6(ngx_conf_t *cf, ngx_
 #if (NGX_HTTP_SSL)
         addrs6[i].conf.ssl = addr[i].opt.ssl;
 #endif
+#if (NGX_HTTP_SPDY)
+        addrs6[i].conf.spdy = addr[i].opt.spdy;
+#endif
 
         if (addr[i].hash.buckets == NULL
             && (addr[i].wc_head == NULL
diff -r 7ecddb37e4c8 -r 0fed3becc056 src/http/ngx_http.h
--- a/src/http/ngx_http.h	Mon Mar 11 18:22:11 2013 +0400
+++ b/src/http/ngx_http.h	Wed Mar 13 02:58:14 2013 +0400
@@ -13,6 +13,7 @@
 #include <ngx_core.h>
 
 
+typedef struct ngx_http_connection_s  ngx_http_connection_t;
 typedef struct ngx_http_request_s     ngx_http_request_t;
 typedef struct ngx_http_upstream_s    ngx_http_upstream_t;
 typedef struct ngx_http_cache_s       ngx_http_cache_t;
@@ -25,6 +26,9 @@ typedef ngx_int_t (*ngx_http_header_hand
 typedef u_char *(*ngx_http_log_handler_pt)(ngx_http_request_t *r,
     ngx_http_request_t *sr, u_char *buf, size_t len);
 
+#if (NGX_HTTP_SPDY)
+#include <ngx_http_spdy.h>
+#endif
 
 #include <ngx_http_variables.h>
 #include <ngx_http_config.h>
@@ -35,6 +39,10 @@ typedef u_char *(*ngx_http_log_handler_p
 #include <ngx_http_busy_lock.h>
 #include <ngx_http_core_module.h>
 
+#if (NGX_HTTP_SPDY)
+#include <ngx_http_spdy_module.h>
+#endif
+
 #if (NGX_HTTP_CACHE)
 #include <ngx_http_cache.h>
 #endif
@@ -80,12 +88,16 @@ ngx_int_t ngx_http_add_listen(ngx_conf_t
 
 
 void ngx_http_init_connection(ngx_connection_t *c);
+void ngx_http_close_connection(ngx_connection_t *c);
 
 #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
 int ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg);
 #endif
 
+ngx_http_request_t *ngx_http_create_request(ngx_connection_t *c);
+
 ngx_int_t ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b);
+ngx_int_t ngx_http_parse_uri(ngx_http_request_t *r);
 ngx_int_t ngx_http_parse_complex_uri(ngx_http_request_t *r,
     ngx_uint_t merge_slashes);
 ngx_int_t ngx_http_parse_status_line(ngx_http_request_t *r, ngx_buf_t *b,
@@ -104,12 +116,15 @@ ngx_int_t ngx_http_parse_chunked(ngx_htt
     ngx_http_chunked_t *ctx);
 
 
+ngx_int_t ngx_http_process_request_header(ngx_http_request_t *r);
+void ngx_http_process_request(ngx_http_request_t *r);
 void ngx_http_update_location_config(ngx_http_request_t *r);
 void ngx_http_handler(ngx_http_request_t *r);
 void ngx_http_run_posted_requests(ngx_connection_t *c);
 ngx_int_t ngx_http_post_request(ngx_http_request_t *r,
     ngx_http_posted_request_t *pr);
 void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc);
+void ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc);
 
 void ngx_http_empty_handler(ngx_event_t *wev);
 void ngx_http_request_empty_handler(ngx_http_request_t *r);
diff -r 7ecddb37e4c8 -r 0fed3becc056 src/http/ngx_http_core_module.c
--- a/src/http/ngx_http_core_module.c	Mon Mar 11 18:22:11 2013 +0400
+++ b/src/http/ngx_http_core_module.c	Wed Mar 13 02:58:14 2013 +0400
@@ -85,7 +85,6 @@ static char *ngx_http_disable_symlinks(n
 #endif
 
 static char *ngx_http_core_lowat_check(ngx_conf_t *cf, void *post, void *data);
-static char *ngx_http_core_pool_size(ngx_conf_t *cf, void *post, void *data);
 
 static ngx_conf_post_t  ngx_http_core_lowat_post =
     { ngx_http_core_lowat_check };
@@ -2130,6 +2129,13 @@ ngx_http_gzip_ok(ngx_http_request_t *r)
         return NGX_DECLINED;
     }
 
+#if (NGX_HTTP_SPDY)
+    if (r->spdy_stream) {
+        r->gzip_ok = 1;
+        return NGX_OK;
+    }
+#endif
+
     ae = r->headers_in.accept_encoding;
     if (ae == NULL) {
         return NGX_DECLINED;
@@ -2464,6 +2470,10 @@ ngx_http_subrequest(ngx_http_request_t *
 
     sr->request_body = r->request_body;
 
+#if (NGX_HTTP_SPDY)
+    sr->spdy_stream = r->spdy_stream;
+#endif
+
     sr->method = NGX_HTTP_GET;
     sr->http_version = r->http_version;
 
@@ -4130,6 +4140,23 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx
 #endif
         }
 
+        if (ngx_strcmp(value[n].data, "spdy") == 0) {
+#if (NGX_HTTP_SPDY)
+            ngx_http_spdy_main_conf_t  *smcf;
+
+            smcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_spdy_module);
+            smcf->enable = 1;
+
+            lsopt.spdy = 1;
+            continue;
+#else
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "the \"spdy\" parameter requires "
+                               "ngx_http_spdy_module");
+            return NGX_CONF_ERROR;
+#endif
+        }
+
         if (ngx_strncmp(value[n].data, "so_keepalive=", 13) == 0) {
 
             if (ngx_strcmp(&value[n].data[13], "on") == 0) {
@@ -5189,7 +5216,7 @@ ngx_http_core_lowat_check(ngx_conf_t *cf
 }
 
 
-static char *
+char *
 ngx_http_core_pool_size(ngx_conf_t *cf, void *post, void *data)
 {
     size_t *sp = data;
diff -r 7ecddb37e4c8 -r 0fed3becc056 src/http/ngx_http_core_module.h
--- a/src/http/ngx_http_core_module.h	Mon Mar 11 18:22:11 2013 +0400
+++ b/src/http/ngx_http_core_module.h	Wed Mar 13 02:58:14 2013 +0400
@@ -75,6 +75,9 @@ typedef struct {
 #if (NGX_HTTP_SSL)
     unsigned                   ssl:1;
 #endif
+#if (NGX_HTTP_SPDY)
+    unsigned                   spdy:1;
+#endif
 #if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
     unsigned                   ipv6only:1;
 #endif
@@ -232,7 +235,10 @@ struct ngx_http_addr_conf_s {
     ngx_http_virtual_names_t  *virtual_names;
 
 #if (NGX_HTTP_SSL)
-    ngx_uint_t                 ssl;   /* unsigned  ssl:1; */
+    unsigned                   ssl:1;
+#endif
+#if (NGX_HTTP_SPDY)
+    unsigned                   spdy:1;
 #endif
 };
 
@@ -528,6 +534,9 @@ ngx_int_t ngx_http_get_forwarded_addr(ng
     int recursive);
 
 
+char *ngx_http_core_pool_size(ngx_conf_t *cf, void *post, void *data);
+
+
 extern ngx_module_t  ngx_http_core_module;
 
 extern ngx_uint_t ngx_http_max_module;
diff -r 7ecddb37e4c8 -r 0fed3becc056 src/http/ngx_http_parse.c
--- a/src/http/ngx_http_parse.c	Mon Mar 11 18:22:11 2013 +0400
+++ b/src/http/ngx_http_parse.c	Wed Mar 13 02:58:14 2013 +0400
@@ -1075,6 +1075,154 @@ header_done:
 
 
 ngx_int_t
+ngx_http_parse_uri(ngx_http_request_t *r)
+{
+    u_char  *p, ch;
+    enum {
+        sw_start = 0,
+        sw_after_slash_in_uri,
+        sw_check_uri,
+        sw_uri
+    } state;
+
+    state = sw_start;
+
+    for (p = r->uri_start; p != r->uri_end; p++) {
+
+        ch = *p;
+
+        switch (state) {
+
+        case sw_start:
+
+            if (ch != '/') {
+                return NGX_ERROR;
+            }
+
+            state = sw_after_slash_in_uri;
+            break;
+
+        /* check "/.", "//", "%", and "\" (Win32) in URI */
+        case sw_after_slash_in_uri:
+
+            if (usual[ch >> 5] & (1 << (ch & 0x1f))) {
+                state = sw_check_uri;
+                break;
+            }
+
+            switch (ch) {
+            case ' ':
+                r->space_in_uri = 1;
+                state = sw_check_uri;
+                break;
+            case '.':
+                r->complex_uri = 1;
+                state = sw_uri;
+                break;
+            case '%':
+                r->quoted_uri = 1;
+                state = sw_uri;
+                break;
+            case '/':
+                r->complex_uri = 1;
+                state = sw_uri;
+                break;
+#if (NGX_WIN32)
+            case '\\':
+                r->complex_uri = 1;
+                state = sw_uri;
+                break;
+#endif
+            case '?':
+                r->args_start = p + 1;
+                state = sw_uri;
+                break;
+            case '#':
+                r->complex_uri = 1;
+                state = sw_uri;
+                break;
+            case '+':
+                r->plus_in_uri = 1;
+                break;
+            default:
+                state = sw_check_uri;
+                break;
+            }
+            break;
+
+        /* check "/", "%" and "\" (Win32) in URI */
+        case sw_check_uri:
+
+            if (usual[ch >> 5] & (1 << (ch & 0x1f))) {
+                break;
+            }
+
+            switch (ch) {
+            case '/':
+#if (NGX_WIN32)
+                if (r->uri_ext == p) {
+                    r->complex_uri = 1;
+                    state = sw_uri;
+                    break;
+                }
+#endif
+                r->uri_ext = NULL;
+                state = sw_after_slash_in_uri;
+                break;
+            case '.':
+                r->uri_ext = p + 1;
+                break;
+            case ' ':
+                r->space_in_uri = 1;
+                break;
+#if (NGX_WIN32)
+            case '\\':
+                r->complex_uri = 1;
+                state = sw_after_slash_in_uri;
+                break;
+#endif
+            case '%':
+                r->quoted_uri = 1;
+                state = sw_uri;
+                break;
+            case '?':
+                r->args_start = p + 1;
+                state = sw_uri;
+                break;
+            case '#':
+                r->complex_uri = 1;
+                state = sw_uri;
+                break;
+            case '+':
+                r->plus_in_uri = 1;
+                break;
+            }
+            break;
+
+        /* URI */
+        case sw_uri:
+
+            if (usual[ch >> 5] & (1 << (ch & 0x1f))) {
+                break;
+            }
+
+            switch (ch) {
+            case ' ':
+                r->space_in_uri = 1;
+                break;
+            case '#':
+                r->complex_uri = 1;
+                break;
+            }
+            break;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
 ngx_http_parse_complex_uri(ngx_http_request_t *r, ngx_uint_t merge_slashes)
 {
     u_char  c, ch, decoded, *p, *u;
diff -r 7ecddb37e4c8 -r 0fed3becc056 src/http/ngx_http_request.c
--- a/src/http/ngx_http_request.c	Mon Mar 11 18:22:11 2013 +0400
+++ b/src/http/ngx_http_request.c	Wed Mar 13 02:58:14 2013 +0400
@@ -11,7 +11,6 @@
 
 
 static void ngx_http_wait_request_handler(ngx_event_t *ev);
-static ngx_http_request_t *ngx_http_create_request(ngx_connection_t *c);
 static void ngx_http_process_request_line(ngx_event_t *rev);
 static void ngx_http_process_request_headers(ngx_event_t *rev);
 static ssize_t ngx_http_read_request_header(ngx_http_request_t *r);
@@ -31,8 +30,6 @@ static ngx_int_t ngx_http_process_connec
 static ngx_int_t ngx_http_process_user_agent(ngx_http_request_t *r,
     ngx_table_elt_t *h, ngx_uint_t offset);
 
-static ngx_int_t ngx_http_process_request_header(ngx_http_request_t *r);
-static void ngx_http_process_request(ngx_http_request_t *r);
 static ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool,
     ngx_uint_t alloc);
 static ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r,
@@ -55,9 +52,7 @@ static void ngx_http_set_lingering_close
 static void ngx_http_lingering_close_handler(ngx_event_t *ev);
 static ngx_int_t ngx_http_post_action(ngx_http_request_t *r);
 static void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t error);
-static void ngx_http_free_request(ngx_http_request_t *r, ngx_int_t error);
 static void ngx_http_log_request(ngx_http_request_t *r);
-static void ngx_http_close_connection(ngx_connection_t *c);
 
 static u_char *ngx_http_log_error(ngx_log_t *log, u_char *buf, size_t len);
 static u_char *ngx_http_log_error_handler(ngx_http_request_t *r,
@@ -317,6 +312,12 @@ ngx_http_init_connection(ngx_connection_
     rev->handler = ngx_http_wait_request_handler;
     c->write->handler = ngx_http_empty_handler;
 
+#if (NGX_HTTP_SPDY)
+    if (hc->addr_conf->spdy) {
+        rev->handler = ngx_http_spdy_init;
+    }
+#endif
+
 #if (NGX_HTTP_SSL)
     {
     ngx_http_ssl_srv_conf_t  *sscf;
@@ -487,7 +488,7 @@ ngx_http_wait_request_handler(ngx_event_
 }
 
 
-static ngx_http_request_t *
+ngx_http_request_t *
 ngx_http_create_request(ngx_connection_t *c)
 {
     ngx_pool_t                 *pool;
@@ -726,6 +727,22 @@ ngx_http_ssl_handshake_handler(ngx_conne
 
         c->ssl->no_wait_shutdown = 1;
 
+#if (NGX_HTTP_SPDY && defined TLSEXT_TYPE_next_proto_neg)
+        {
+        const u_char  *data;
+        unsigned int   len;
+
+        SSL_get0_next_proto_negotiated(c->ssl->connection, &data, &len);
+
+        if (len == sizeof("spdy/2") - 1
+            && ngx_strncmp(data, "spdy/2", sizeof("spdy/2") - 1) == 0)
+        {
+            ngx_http_spdy_init(c->read);
+            return;
+        }
+        }
+#endif
+
         c->log->action = "waiting for request";
 
         c->read->handler = ngx_http_wait_request_handler;
@@ -1682,7 +1699,7 @@ ngx_http_process_multi_header_lines(ngx_
 }
 
 
-static ngx_int_t
+ngx_int_t
 ngx_http_process_request_header(ngx_http_request_t *r)
 {
     if (r->headers_in.server.len == 0
@@ -1752,7 +1769,7 @@ ngx_http_process_request_header(ngx_http
 }
 
 
-static void
+void
 ngx_http_process_request(ngx_http_request_t *r)
 {
     ngx_connection_t  *c;
@@ -2429,6 +2446,13 @@ ngx_http_finalize_connection(ngx_http_re
 {
     ngx_http_core_loc_conf_t  *clcf;
 
+#if (NGX_HTTP_SPDY)
+    if (r->spdy_stream) {
+        ngx_http_close_request(r, 0);
+        return;
+    }
+#endif
+
     clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
 
     if (r->main->count != 1) {
@@ -2483,6 +2507,12 @@ ngx_http_set_write_handler(ngx_http_requ
                                 ngx_http_test_reading;
     r->write_event_handler = ngx_http_writer;
 
+#if (NGX_HTTP_SPDY)
+    if (r->spdy_stream) {
+        return NGX_OK;
+    }
+#endif
+
     wev = r->connection->write;
 
     if (wev->ready && wev->delayed) {
@@ -2630,6 +2660,19 @@ ngx_http_test_reading(ngx_http_request_t
 
     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http test reading");
 
+#if (NGX_HTTP_SPDY)
+
+    if (r->spdy_stream) {
+        if (c->error) {
+            err = 0;
+            goto closed;
+        }
+
+        return;
+    }
+
+#endif
+
 #if (NGX_HAVE_KQUEUE)
 
     if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
@@ -3265,12 +3308,19 @@ ngx_http_close_request(ngx_http_request_
         return;
     }
 
+#if (NGX_HTTP_SPDY)
+    if (r->spdy_stream) {
+        ngx_http_spdy_close_stream(r->spdy_stream, rc);
+        return;
+    }
+#endif
+
     ngx_http_free_request(r, rc);
     ngx_http_close_connection(c);
 }
 
 
-static void
+void
 ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc)
 {
     ngx_log_t                 *log;
@@ -3371,7 +3421,7 @@ ngx_http_log_request(ngx_http_request_t 
 }
 
 
-static void
+void
 ngx_http_close_connection(ngx_connection_t *c)
 {
     ngx_pool_t  *pool;
diff -r 7ecddb37e4c8 -r 0fed3becc056 src/http/ngx_http_request.h
--- a/src/http/ngx_http_request.h	Mon Mar 11 18:22:11 2013 +0400
+++ b/src/http/ngx_http_request.h	Wed Mar 13 02:58:14 2013 +0400
@@ -291,7 +291,7 @@ typedef struct {
 
 typedef struct ngx_http_addr_conf_s  ngx_http_addr_conf_t;
 
-typedef struct {
+struct ngx_http_connection_s {
     ngx_http_addr_conf_t             *addr_conf;
     ngx_http_conf_ctx_t              *conf_ctx;
 
@@ -311,7 +311,7 @@ typedef struct {
 #if (NGX_HTTP_SSL)
     ngx_uint_t                        ssl;    /* unsigned  ssl:1; */
 #endif
-} ngx_http_connection_t;
+};
 
 
 typedef void (*ngx_http_cleanup_pt)(void *data);
@@ -429,6 +429,9 @@ struct ngx_http_request_s {
     ngx_uint_t                        err_status;
 
     ngx_http_connection_t            *http_connection;
+#if (NGX_HTTP_SPDY)
+    ngx_http_spdy_stream_t           *spdy_stream;
+#endif
 
     ngx_http_log_handler_pt           log_handler;
 
diff -r 7ecddb37e4c8 -r 0fed3becc056 src/http/ngx_http_request_body.c
--- a/src/http/ngx_http_request_body.c	Mon Mar 11 18:22:11 2013 +0400
+++ b/src/http/ngx_http_request_body.c	Wed Mar 13 02:58:14 2013 +0400
@@ -42,6 +42,30 @@ ngx_http_read_client_request_body(ngx_ht
 
     r->main->count++;
 
+#if (NGX_HTTP_SPDY)
+    if (r->spdy_stream) {
+
+        if (r->spdy_stream->skip_data) {
+            post_handler(r);
+            return NGX_OK;
+        }
+
+        if (!r->request_body && ngx_http_spdy_init_request_body(r) != NGX_OK) {
+            rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+            goto done;
+        }
+
+        if (r->spdy_stream->half_closed) {
+            post_handler(r);
+            return NGX_OK;
+        }
+
+        r->request_body->post_handler = post_handler;
+
+        return NGX_AGAIN;
+    }
+#endif
+
     if (r->request_body || r->discard_body) {
         post_handler(r);
         return NGX_OK;
@@ -462,6 +486,13 @@ ngx_http_discard_request_body(ngx_http_r
     ngx_int_t     rc;
     ngx_event_t  *rev;
 
+#if (NGX_HTTP_SPDY)
+    if (r->spdy_stream && r == r->main) {
+        r->spdy_stream->skip_data = 1;
+        return NGX_OK;
+    }
+#endif
+
     if (r != r->main || r->discard_body || r->request_body) {
         return NGX_OK;
     }
diff -r 7ecddb37e4c8 -r 0fed3becc056 src/http/ngx_http_spdy.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/http/ngx_http_spdy.c	Wed Mar 13 02:58:14 2013 +0400
@@ -0,0 +1,2827 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Valentin V. Bartenev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#include <zlib.h>
+
+
+#if (NGX_HAVE_GCC_ATTRIBUTE_ALIGNED)
+#define ngx_aligned(x)        __attribute__(( aligned (x) ))
+#else
+#define ngx_aligned(x)
+#endif
+
+
+#if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED)
+
+#define ngx_str5cmp(m, c0, c1, c2, c3, c4)                                    \
+    *(uint32_t *) m == (c3 << 24 | c2 << 16 | c1 << 8 | c0)                   \
+        && m[4] == c4
+
+#else
+
+#define ngx_str5cmp(m, c0, c1, c2, c3, c4)                                    \
+    m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3 && m[4] == c4
+
+#endif
+
+
+#if (NGX_HAVE_NONALIGNED)
+
+#define ngx_spdy_frame_parse_uint16(p)  ntohs(*(uint16_t *) (p))
+#define ngx_spdy_frame_parse_uint32(p)  ntohl(*(uint32_t *) (p))
+#define ngx_spdy_frame_parse_len(p)    (ntohl(*(uint32_t *) (p)) >> 8)
+#define ngx_spdy_frame_parse_sid(p)    (ntohl(*(uint32_t *) (p)) & 0x7fffffff)
+
+#else
+
+#define ngx_spdy_frame_parse_uint16(p) ((p)[0] << 8 | (p)[1])
+#define ngx_spdy_frame_parse_uint32(p)                                        \
+    ((p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3])
+#define ngx_spdy_frame_parse_len(p)    ((p)[0] << 16 | (p)[1] << 8 | (p)[2])
+#define ngx_spdy_frame_parse_sid(p)                                           \
+    (((p)[0] & 0x7f) << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3])
+
+#endif
+
+
+#define NGX_SPDY_HEADER_SIZE          8
+
+#define NGX_SPDY_CTRL_BYTE            0x80
+
+#define NGX_SPDY_SYN_STREAM           1
+#define NGX_SPDY_SYN_REPLY            2
+#define NGX_SPDY_RST_STREAM           3
+#define NGX_SPDY_SETTINGS             4
+#define NGX_SPDY_NOOP                 5
+#define NGX_SPDY_PING                 6
+#define NGX_SPDY_GOAWAY               7
+#define NGX_SPDY_HEADERS              8
+
+#define NGX_SPDY_RST_STREAM_SIZE      8
+#define NGX_SPDY_PING_SIZE            4
+#define NGX_SPDY_GOAWAY_SIZE          4
+
+#define NGX_SPDY_FLAG_CLEAR_SETTINGS  0x01
+
+
+#if (NGX_HAVE_LITTLE_ENDIAN)
+
+#define NGX_SPDY_SYN_STREAM_HEAD                                              \
+    (NGX_SPDY_SYN_STREAM << 24 | NGX_SPDY_VERSION << 8 | NGX_SPDY_CTRL_BYTE)
+
+#define NGX_SPDY_SYN_REPLY_HEAD                                               \
+    (NGX_SPDY_SYN_REPLY << 24 | NGX_SPDY_VERSION << 8 | NGX_SPDY_CTRL_BYTE)
+
+#define NGX_SPDY_RST_STREAM_HEAD                                              \
+    (NGX_SPDY_RST_STREAM << 24 | NGX_SPDY_VERSION << 8 | NGX_SPDY_CTRL_BYTE)
+
+#define NGX_SPDY_SETTINGS_HEAD                                                \
+    (NGX_SPDY_SETTINGS << 24 | NGX_SPDY_VERSION << 8 | NGX_SPDY_CTRL_BYTE)
+
+#define NGX_SPDY_NOOP_HEAD                                                    \
+    (NGX_SPDY_NOOP << 24 | NGX_SPDY_VERSION << 8 | NGX_SPDY_CTRL_BYTE)
+
+#define NGX_SPDY_PING_HEAD                                                    \
+    (NGX_SPDY_PING << 24 | NGX_SPDY_VERSION << 8 | NGX_SPDY_CTRL_BYTE)
+
+#define NGX_SPDY_GOAWAY_HEAD                                                  \
+    (NGX_SPDY_GOAWAY << 24 | NGX_SPDY_VERSION << 8 | NGX_SPDY_CTRL_BYTE)
+
+#define NGX_SPDY_HEADERS_HEAD                                                 \
+    (NGX_SPDY_HEADERS << 24 | NGX_SPDY_VERSION << 8 | NGX_SPDY_CTRL_BYTE)
+
+#else
+
+#define NGX_SPDY_SYN_STREAM_HEAD                                              \
+    (NGX_SPDY_CTRL_BYTE << 24 | NGX_SPDY_VERSION << 16 | NGX_SPDY_SYN_STREAM)
+
+#define NGX_SPDY_SYN_REPLY_HEAD                                               \
+    (NGX_SPDY_CTRL_BYTE << 24 | NGX_SPDY_VERSION << 16 | NGX_SPDY_SYN_REPLY)
+
+#define NGX_SPDY_RST_STREAM_HEAD                                              \
+    (NGX_SPDY_CTRL_BYTE << 24 | NGX_SPDY_VERSION << 16 | NGX_SPDY_RST_STREAM)
+
+#define NGX_SPDY_SETTINGS_HEAD                                                \
+    (NGX_SPDY_CTRL_BYTE << 24 | NGX_SPDY_VERSION << 16 | NGX_SPDY_SETTINGS)
+
+#define NGX_SPDY_NOOP_HEAD                                                    \
+    (NGX_SPDY_CTRL_BYTE << 24 | NGX_SPDY_VERSION << 16 | NGX_SPDY_NOOP)
+
+#define NGX_SPDY_PING_HEAD                                                    \
+    (NGX_SPDY_CTRL_BYTE << 24 | NGX_SPDY_VERSION << 16 | NGX_SPDY_PING)
+
+#define NGX_SPDY_GOAWAY_HEAD                                                  \
+    (NGX_SPDY_CTRL_BYTE << 24 | NGX_SPDY_VERSION << 16 | NGX_SPDY_GOAWAY)
+
+#define NGX_SPDY_HEADERS_HEAD                                                 \
+    (NGX_SPDY_CTRL_BYTE << 24 | NGX_SPDY_VERSION << 16 | NGX_SPDY_HEADERS)
+
+#endif
+
+
+#define NGX_SPDY_PROTOCOL_ERROR            1
+#define NGX_SPDY_INVALID_STREAM            2
+#define NGX_SPDY_REFUSED_STREAM            3
+#define NGX_SPDY_UNSUPPORTED_VERSION       4
+#define NGX_SPDY_CANCEL                    5
+#define NGX_SPDY_INTERNAL_ERROR            6
+#define NGX_SPDY_FLOW_CONTROL_ERROR        7
+
+#define NGX_SPDY_SKIP_HEADERS_BUFFER_SIZE  4096
+
+#define NGX_SPDY_CTRL_FRAME_BUFFER_SIZE    16
+
+
+typedef struct {
+    ngx_uint_t    hash;
+    ngx_str_t     header;
+    ngx_int_t   (*handler)(ngx_http_request_t *r);
+} ngx_http_spdy_request_header_t;
+
+
+typedef struct {
+    u_char        len;
+    u_char        method[11];
+    uint32_t      value;
+} ngx_http_spdy_method_test_t;
+
+
+static u_char ngx_http_spdy_dict[] =
+    "options" "get" "head" "post" "put" "delete" "trace"
+    "accept" "accept-charset" "accept-encoding" "accept-language"
+    "authorization" "expect" "from" "host"
+    "if-modified-since" "if-match" "if-none-match" "if-range"
+    "if-unmodifiedsince" "max-forwards" "proxy-authorization"
+    "range" "referer" "te" "user-agent"
+    "100" "101" "200" "201" "202" "203" "204" "205" "206"
+    "300" "301" "302" "303" "304" "305" "306" "307"
+    "400" "401" "402" "403" "404" "405" "406" "407" "408" "409" "410"
+    "411" "412" "413" "414" "415" "416" "417"
+    "500" "501" "502" "503" "504" "505"
+    "accept-ranges" "age" "etag" "location" "proxy-authenticate" "public"
+    "retry-after" "server" "vary" "warning" "www-authenticate" "allow"
+    "content-base" "content-encoding" "cache-control" "connection" "date"
+    "trailer" "transfer-encoding" "upgrade" "via" "warning"
+    "content-language" "content-length" "content-location"
+    "content-md5" "content-range" "content-type" "etag" "expires"
+    "last-modified" "set-cookie"
+    "Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday" "Sunday"
+    "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"
+    "chunked" "text/html" "image/png" "image/jpg" "image/gif"
+    "application/xml" "application/xhtml" "text/plain" "public" "max-age"
+    "charset=iso-8859-1" "utf-8" "gzip" "deflate" "HTTP/1.1" "status"
+    "version" "url";
+
+static void *ngx_http_spdy_zalloc(void *opaque, u_int items, u_int size);
+static void ngx_http_spdy_zfree(void *opaque, void *address);
+
+static void ngx_http_spdy_pool_cleanup(void *data);
+
+static void ngx_http_spdy_read_handler(ngx_event_t *rev);
+static void ngx_http_spdy_write_handler(ngx_event_t *wev);
+static void ngx_http_spdy_keepalive_handler(ngx_event_t *rev);
+
+#define ngx_http_spdy_streams_index_size(sscf)  (sscf->streams_index_mask + 1)
+#define ngx_http_spdy_stream_index(sscf, sid)                                 \
+    ((sid >> 1) & sscf->streams_index_mask)
+static ngx_http_spdy_stream_t *ngx_http_spdy_get_stream_by_id(
+    ngx_http_spdy_connection_t *sc, ngx_uint_t sid);
+
+static void ngx_http_spdy_run_request(ngx_http_request_t *r);
+
+static void ngx_http_spdy_handle_connection(ngx_http_spdy_connection_t *sc);
+static void ngx_http_spdy_finalize_connection(ngx_http_spdy_connection_t *sc,
+    ngx_int_t rc);
+
+static ngx_int_t ngx_http_spdy_process_frame(ngx_http_spdy_connection_t *sc,
+    u_char **pos, size_t size);
+static ngx_int_t ngx_http_spdy_detect_settings_frame(
+    ngx_http_spdy_connection_t *sc, u_char **pos, size_t size);
+static ngx_int_t ngx_http_spdy_process_settings_frame(
+    ngx_http_spdy_connection_t *sc, u_char **pos, size_t size);
+static ngx_int_t ngx_http_spdy_skip_frame(ngx_http_spdy_connection_t *sc,
+    u_char **pos, size_t size);
+static ngx_int_t ngx_http_spdy_process_syn_stream(
+    ngx_http_spdy_connection_t *sc, u_char **pos, size_t size);
+static ngx_int_t ngx_http_spdy_process_data_frame(
+    ngx_http_spdy_connection_t *sc, u_char **pos, size_t size);
+static ngx_int_t ngx_http_spdy_process_rst_stream(
+    ngx_http_spdy_connection_t *sc, u_char **pos, size_t size);
+static ngx_int_t ngx_http_spdy_process_ping(ngx_http_spdy_connection_t *sc,
+    u_char **pos, size_t size);
+static ngx_int_t ngx_http_spdy_process_headers(ngx_http_spdy_connection_t *sc,
+    u_char **pos, size_t size);
+static ngx_int_t ngx_http_spdy_skip_headers(ngx_http_spdy_connection_t *sc,
+    u_char **pos, size_t size);
+
+static ngx_int_t ngx_http_spdy_parse_header(ngx_http_request_t *r);
+static ngx_int_t ngx_http_spdy_handle_request_headers(ngx_http_request_t *r);
+static ngx_int_t ngx_http_spdy_parse_method(ngx_http_request_t *r);
+static ngx_int_t ngx_http_spdy_parse_scheme(ngx_http_request_t *r);
+static ngx_int_t ngx_http_spdy_parse_url(ngx_http_request_t *r);
+static ngx_int_t ngx_http_spdy_parse_version(ngx_http_request_t *r);
+
+static ngx_int_t ngx_http_spdy_alloc_large_header_buffer(ngx_http_request_t *r);
+static ngx_int_t ngx_http_spdy_construct_request_line(ngx_http_request_t *r);
+
+static ngx_http_spdy_out_frame_t *ngx_http_spdy_get_ctrl_frame(
+    ngx_http_spdy_connection_t *sc, size_t size, ngx_uint_t priority);
+static ngx_int_t ngx_http_spdy_ctrl_frame_handler(
+    ngx_http_spdy_connection_t *sc, ngx_http_spdy_out_frame_t *frame);
+
+static ngx_int_t ngx_http_spdy_send_rst_stream(ngx_http_spdy_connection_t *sc,
+    ngx_uint_t sid, ngx_uint_t status, ngx_uint_t priority);
+
+static ngx_int_t ngx_http_spdy_send_settings(ngx_http_spdy_connection_t *sc);
+static ngx_int_t ngx_http_spdy_settings_frame_handler(
+    ngx_http_spdy_connection_t *sc, ngx_http_spdy_out_frame_t *frame);
+
+
+static ngx_http_spdy_request_header_t ngx_http_spdy_request_headers[] = {
+    { 0, ngx_string("method"), ngx_http_spdy_parse_method },
+    { 0, ngx_string("scheme"), ngx_http_spdy_parse_scheme },
+    { 0, ngx_string("url"), ngx_http_spdy_parse_url },
+    { 0, ngx_string("version"), ngx_http_spdy_parse_version },
+};
+
+#define NGX_SPDY_REQUEST_HEADERS                                              \
+    (sizeof(ngx_http_spdy_request_headers)                                    \
+     / sizeof(ngx_http_spdy_request_header_t))
+
+
+void
+ngx_http_spdy_init(ngx_event_t *rev)
+{
+    int                          rc;
+    ngx_connection_t            *c;
+    ngx_pool_cleanup_t          *cln;
+    ngx_http_spdy_srv_conf_t    *sscf;
+    ngx_http_spdy_connection_t  *sc;
+
+    c = rev->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "init spdy request");
+
+    c->log->action = "SPDY processing";
+
+    sc = ngx_pcalloc(c->pool, sizeof(ngx_http_spdy_connection_t));
+    if (sc == NULL) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    sc->connection = c;
+    sc->http_connection = c->data;
+
+    sc->handler = ngx_http_spdy_detect_settings_frame;
+
+    sc->zstream_in.zalloc = ngx_http_spdy_zalloc;
+    sc->zstream_in.zfree = ngx_http_spdy_zfree;
+    sc->zstream_in.opaque = sc;
+
+    rc = inflateInit(&sc->zstream_in);
+    if (rc != Z_OK) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                      "inflateInit() failed: %d", rc);
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    sc->zstream_out.zalloc = ngx_http_spdy_zalloc;
+    sc->zstream_out.zfree = ngx_http_spdy_zfree;
+    sc->zstream_out.opaque = sc;
+
+    sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx,
+                                        ngx_http_spdy_module);
+
+    rc = deflateInit2(&sc->zstream_out, (int) sscf->headers_comp,
+                      Z_DEFLATED, 11, 4, Z_DEFAULT_STRATEGY);
+
+    if (rc != Z_OK) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                      "deflateInit2() failed: %d", rc);
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    rc = deflateSetDictionary(&sc->zstream_out, ngx_http_spdy_dict,
+                              sizeof(ngx_http_spdy_dict));
+    if (rc != Z_OK) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                      "deflateSetDictionary() failed: %d", rc);
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    sc->pool = ngx_create_pool(sscf->pool_size, sc->connection->log);
+    if (sc->pool == NULL) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_pool_cleanup_file_t));
+    if (cln == NULL) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    cln->handler = ngx_http_spdy_pool_cleanup;
+    cln->data = sc;
+
+    sc->streams_index = ngx_pcalloc(sc->pool,
+                                    ngx_http_spdy_streams_index_size(sscf)
+                                    * sizeof(ngx_http_spdy_stream_t *));
+    if (sc->streams_index == NULL) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    c->data = sc;
+
+    rev->handler = ngx_http_spdy_read_handler;
+    c->write->handler = ngx_http_spdy_write_handler;
+
+    ngx_http_spdy_read_handler(rev);
+}
+
+
+static void
+ngx_http_spdy_pool_cleanup(void *data)
+{
+    ngx_http_spdy_connection_t  *sc = data;
+
+    if (sc->pool) {
+        ngx_destroy_pool(sc->pool);
+    }
+}
+
+
+void
+ngx_http_spdy_request_headers_init()
+{
+    ngx_uint_t                       i;
+    ngx_http_spdy_request_header_t  *h;
+
+    for (i = 0; i < NGX_SPDY_REQUEST_HEADERS; i++) {
+        h = &ngx_http_spdy_request_headers[i];
+        h->hash = ngx_hash_key(h->header.data, h->header.len);
+    }
+}
+
+
+static void *
+ngx_http_spdy_zalloc(void *opaque, u_int items, u_int size)
+{
+    ngx_http_spdy_connection_t *sc = opaque;
+
+    return ngx_palloc(sc->connection->pool, items * size);
+}
+
+
+static void
+ngx_http_spdy_zfree(void *opaque, void *address)
+{
+#if 0
+    ngx_http_spdy_connection_t *sc = opaque;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+                   "spdy zfree: %p", address);
+#endif
+}
+
+
+static ngx_http_spdy_stream_t *
+ngx_http_spdy_create_stream(ngx_http_spdy_connection_t *sc, ngx_uint_t id,
+    ngx_uint_t priority)
+{
+    ngx_log_t                 *log;
+    ngx_uint_t                 index;
+    ngx_event_t               *rev, *wev;
+    ngx_connection_t          *fc;
+    ngx_http_log_ctx_t        *ctx;
+    ngx_http_request_t        *r;
+    ngx_http_spdy_stream_t    *stream;
+    ngx_http_core_srv_conf_t  *cscf;
+    ngx_http_spdy_srv_conf_t  *sscf;
+
+    fc = sc->free_fake_connections;
+
+    if (fc) {
+        sc->free_fake_connections = fc->data;
+
+        rev = fc->read;
+        wev = fc->write;
+        log = fc->log;
+        ctx = log->data;
+
+    } else {
+        fc = ngx_palloc(sc->pool, sizeof(ngx_connection_t));
+        if (fc == NULL) {
+            return NULL;
+        }
+
+        rev = ngx_palloc(sc->pool, sizeof(ngx_event_t));
+        if (rev == NULL) {
+            return NULL;
+        }
+
+        wev = ngx_palloc(sc->pool, sizeof(ngx_event_t));
+        if (wev == NULL) {
+            return NULL;
+        }
+
+        log = ngx_palloc(sc->pool, sizeof(ngx_log_t));
+        if (log == NULL) {
+            return NULL;
+        }
+
+        ctx = ngx_palloc(sc->pool, sizeof(ngx_http_log_ctx_t));
+        if (ctx == NULL) {
+            return NULL;
+        }
+
+        ctx->connection = fc;
+        ctx->request = NULL;
+    }
+
+    ngx_memcpy(log, sc->connection->log, sizeof(ngx_log_t));
+
+    log->data = ctx;
+
+    ngx_memzero(rev, sizeof(ngx_event_t));
+
+    rev->data = fc;
+    rev->ready = 1;
+    rev->handler = ngx_http_empty_handler;
+    rev->log = log;
+
+    ngx_memcpy(wev, rev, sizeof(ngx_event_t));
+
+    wev->write = 1;
+
+    ngx_memcpy(fc, sc->connection, sizeof(ngx_connection_t));
+
+    fc->data = sc->http_connection;
+    fc->read = rev;
+    fc->write = wev;
+    fc->sent = 0;
+    fc->log = log;
+    fc->buffered = 0;
+    fc->sndlowat = 1;
+
+    r = ngx_http_create_request(fc);
+    if (r == NULL) {
+        return NULL;
+    }
+
+    fc->data = r;
+    sc->connection->requests++;
+
+    cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+    r->header_in = ngx_create_temp_buf(r->pool,
+                                       cscf->client_header_buffer_size);
+    if (r->header_in == NULL) {
+        ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return NULL;
+    }
+
+    r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE;
+
+    r->valid_location = 1;
+
+    r->gzip_tested = 1;
+    r->gzip_ok = 1;
+
+    stream = ngx_pcalloc(r->pool, sizeof(ngx_http_spdy_stream_t));
+    if (stream == NULL) {
+        ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return NULL;
+    }
+
+    r->spdy_stream = stream;
+
+    stream->id = id;
+    stream->request = r;
+    stream->connection = sc;
+    stream->priority = priority;
+
+    sscf = ngx_http_get_module_srv_conf(r, ngx_http_spdy_module);
+
+    index = ngx_http_spdy_stream_index(sscf, id);
+
+    stream->index = sc->streams_index[index];
+    sc->streams_index[index] = stream;
+
+    sc->processing++;
+
+    return stream;
+}
+
+
+static void
+ngx_http_spdy_read_handler(ngx_event_t *rev)
+{
+    u_char                      *p, *end;
+    size_t                       available;
+    ssize_t                      n;
+    ngx_int_t                    rc;
+    ngx_uint_t                   rest;
+    ngx_connection_t            *c;
+    ngx_http_spdy_main_conf_t   *smcf;
+    ngx_http_spdy_connection_t  *sc;
+
+    c = rev->data;
+    sc = c->data;
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+        ngx_http_spdy_finalize_connection(sc, NGX_HTTP_REQUEST_TIME_OUT);
+        return;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "spdy read handler");
+
+    sc->blocked = 1;
+
+    smcf = ngx_http_get_module_main_conf(sc->http_connection->conf_ctx,
+                                         ngx_http_spdy_module);
+
+    available = smcf->recv_buffer_size - NGX_SPDY_STATE_BUFFER_SIZE + 1;
+
+    rc = sc->waiting ? NGX_AGAIN : NGX_DONE;
+    rest = sc->buffer_used;
+
+    do {
+        p = smcf->recv_buffer;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                               "SPDY rest %ui", rest);
+
+        ngx_memcpy(p, sc->buffer, NGX_SPDY_STATE_BUFFER_SIZE);
+
+        n = c->recv(c, p + rest, available - rest);
+
+        if (n == NGX_AGAIN) {
+            break;
+        }
+
+        if (n == 0 && (sc->waiting || sc->processing)) {
+            ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                          "client closed prematurely connection");
+        }
+
+        if (n == 0 || n == NGX_ERROR) {
+            ngx_http_spdy_finalize_connection(sc,
+                                              NGX_HTTP_CLIENT_CLOSED_REQUEST);
+            return;
+        }
+
+        n += rest;
+        end = p + n;
+
+        do {
+            rc = sc->handler(sc, &p, n);
+
+            n = end - p;
+
+            if (rc == NGX_AGAIN) {
+                ngx_memcpy(sc->buffer, p, NGX_SPDY_STATE_BUFFER_SIZE);
+                break;
+            }
+
+            if (rc == NGX_ERROR) {
+                ngx_log_error(NGX_LOG_WARN, c->log, 0, "SPDY ERROR");
+                ngx_http_spdy_finalize_connection(sc,
+                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+                return;
+            }
+
+        } while (n);
+
+        rest = n;
+
+#if (NGX_DEBUG)
+        if (rest > NGX_SPDY_STATE_BUFFER_SIZE) {
+            ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                          "spdy state buffer overflow: "
+                          "%i bytes required", n);
+            ngx_http_spdy_finalize_connection(sc, NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+#endif
+
+    } while (rev->ready);
+
+    sc->buffer_used = rest;
+
+    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+        ngx_http_spdy_finalize_connection(sc, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    sc->blocked = 0;
+
+    if (rc == NGX_AGAIN) {
+        sc->waiting = 1;
+    }
+
+    if (sc->processing) {
+        if (rev->timer_set) {
+            ngx_del_timer(rev);
+        }
+        return;
+    }
+
+    ngx_http_spdy_handle_connection(sc);
+}
+
+
+static void
+ngx_http_spdy_write_handler(ngx_event_t *wev)
+{
+    ngx_int_t                    rc;
+    ngx_connection_t            *c;
+    ngx_http_spdy_stream_t      *stream, *s, *sn;
+    ngx_http_spdy_connection_t  *sc;
+
+    c = wev->data;
+
+    if (wev->timedout) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "spdy write event timed out");
+        return; /* FIXME */
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "spdy write handler");
+
+    sc = c->data;
+
+    sc->blocked = 2;
+
+    rc = ngx_http_spdy_send_output_queue(sc);
+
+    if (rc == NGX_ERROR) {
+        ngx_http_spdy_finalize_connection(sc, NGX_HTTP_CLIENT_CLOSED_REQUEST);
+        return;
+    }
+
+    stream = NULL;
+
+    for (s = sc->last_stream; s; s = sn) {
+         sn = s->next;
+         s->next = stream;
+         stream = s;
+    }
+
+    sc->last_stream = NULL;
+
+    sc->blocked = 1;
+
+    for ( /* void */ ; stream; stream = sn) {
+        sn = stream->next;
+        stream->handled = 0;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "spdy run stream %ui", stream->id);
+
+        wev = stream->request->connection->write;
+        wev->handler(wev);
+    }
+
+    sc->blocked = 0;
+
+    if (rc == NGX_AGAIN) {
+        return;
+    }
+
+    ngx_http_spdy_handle_connection(sc);
+}
+
+
+ngx_int_t
+ngx_http_spdy_send_output_queue(ngx_http_spdy_connection_t *sc)
+{
+    ngx_chain_t                *cl;
+    ngx_connection_t           *c;
+    ngx_http_core_loc_conf_t   *clcf;
+    ngx_http_spdy_out_frame_t  *out, *frame, *fn;
+
+    c = sc->connection;
+
+    if (c->error) {
+        return NGX_ERROR;
+    }
+
+    if (!c->write->ready) {
+        return NGX_OK;
+    }
+
+    cl = NULL;
+    out = NULL;
+
+    for (frame = sc->last_out; frame; frame = fn) {
+        frame->last->next = cl;
+        cl = frame->first;
+
+        fn = frame->next;
+        frame->next = out;
+        out = frame;
+
+        ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "spdy frame out: %p sid:%ui prio:%ui bl:%ui size:%uz",
+                       out, out->stream ? out->stream->id : 0, out->priority,
+                       out->blocked, out->size);
+    }
+
+    cl = c->send_chain(c, cl, 0);
+
+    if (cl == NGX_CHAIN_ERROR) {
+        c->error = 1;
+
+        if (!sc->blocked) {
+            ngx_post_event(c->write, &ngx_posted_events);
+        }
+
+        return NGX_ERROR;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(sc->http_connection->conf_ctx,
+                                        ngx_http_core_module);
+
+    if (ngx_handle_write_event(c->write, clcf->send_lowat) != NGX_OK) {
+        return NGX_ERROR; /* FIXME */
+    }
+
+    for ( /* void */ ; out; out = out->next) {
+        if (out->handler(sc, out) != NGX_OK) {
+            out->blocked = 1;
+            out->priority = NGX_SPDY_HIGHEST_PRIORITY;
+            break;
+        }
+
+        ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "spdy frame sent: %p sid:%ui bl:%ui size:%uz",
+                       out, out->stream ? out->stream->id : 0,
+                       out->blocked, out->size);
+    }
+
+    frame = NULL;
+
+    for ( /* void */ ; out; out = fn) {
+        fn = out->next;
+        out->next = frame;
+        frame = out;
+    }
+
+    sc->last_out = frame;
+
+    return NGX_OK;
+}
+
+
+static ngx_http_spdy_stream_t *
+ngx_http_spdy_get_stream_by_id(ngx_http_spdy_connection_t *sc,
+    ngx_uint_t sid)
+{
+    ngx_http_spdy_stream_t    *stream;
+    ngx_http_spdy_srv_conf_t  *sscf;
+
+    sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx,
+                                        ngx_http_spdy_module);
+
+    stream = sc->streams_index[ngx_http_spdy_stream_index(sscf, sid)];
+
+    while (stream) {
+        if (stream->id == sid) {
+            return stream;
+        }
+
+        stream = stream->index;
+    }
+
+    return NULL;
+}
+
+
+static ngx_int_t
+ngx_http_spdy_process_frame(ngx_http_spdy_connection_t *sc, u_char **pos,
+    size_t size)
+{
+    u_char                  *p, flags;
+    size_t                   length;
+    uint32_t                 head;
+    ngx_http_spdy_stream_t  *stream;
+
+    if (size < NGX_SPDY_HEADER_SIZE) {
+        return NGX_AGAIN;
+    }
+
+    p = *pos;
+
+#if (NGX_HAVE_NONALIGNED)
+    head = *(uint32_t *) p;
+#else
+
+#if (NGX_HAVE_LITTLE_ENDIAN)
+    head = p[0] | p[1] << 8 | p[2] << 16 | p[3] << 24;
+#else
+    head = p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3];
+#endif
+
+#endif
+
+    flags = p[4];
+    length = ngx_spdy_frame_parse_len(p + 5);
+
+    sc->length = length;
+    sc->flags = flags;
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+                   "spdy process frame head:%08Xd f:%ui l:%ui",
+                   head, flags, length);
+
+    *pos += NGX_SPDY_HEADER_SIZE;
+
+    switch (head) {
+
+    case NGX_SPDY_SYN_STREAM_HEAD:
+        sc->handler = ngx_http_spdy_process_syn_stream;
+        return NGX_OK;
+
+    case NGX_SPDY_SYN_REPLY_HEAD:
+        /* TODO */
+        return NGX_ERROR;
+
+    case NGX_SPDY_RST_STREAM_HEAD:
+        sc->handler = ngx_http_spdy_process_rst_stream;
+        return NGX_OK;
+
+    case NGX_SPDY_SETTINGS_HEAD:
+        /* TODO */
+        sc->handler = ngx_http_spdy_skip_frame;
+        return NGX_OK;
+
+    case NGX_SPDY_NOOP_HEAD:
+        if (flags != 0 || length != 0) {
+            /* TODO */
+            return NGX_ERROR;
+        }
+        return NGX_OK;
+
+    case NGX_SPDY_PING_HEAD:
+        sc->handler = ngx_http_spdy_process_ping;
+        return NGX_OK;
+
+    case NGX_SPDY_GOAWAY_HEAD:
+        /* TODO */
+        sc->handler = ngx_http_spdy_skip_frame;
+        return NGX_OK;
+
+    case NGX_SPDY_HEADERS_HEAD:
+        /* TODO */
+        return NGX_ERROR;
+    }
+
+    head = ntohl(head);
+
+    if (head >> 31) {
+        /* TODO version & type check */
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+                       "spdy unknown frame %ui", head);
+
+        sc->handler = ngx_http_spdy_skip_frame;
+
+        return NGX_OK;
+    }
+
+    stream = ngx_http_spdy_get_stream_by_id(sc, head);
+
+    if (stream == NULL) {
+        sc->handler = ngx_http_spdy_skip_frame;
+        return NGX_OK;
+    }
+
+    if (stream->half_closed) {
+        /* TODO log && error handling */
+        return NGX_ERROR;
+    }
+
+    stream->request->request_length += size;
+
+    if (stream->skip_data) {
+        sc->handler = ngx_http_spdy_skip_frame;
+        return NGX_OK;
+    }
+
+    sc->stream = stream;
+    sc->handler = ngx_http_spdy_process_data_frame;
+
+    return ngx_http_spdy_process_data_frame(sc, pos, size - 8);
+}
+
+
+static ngx_int_t
+ngx_http_spdy_detect_settings_frame(ngx_http_spdy_connection_t *sc,
+    u_char **pos, size_t size)
+{
+    u_char  *p;
+
+    if (size < NGX_SPDY_HEADER_SIZE) {
+        return NGX_AGAIN;
+    }
+
+    p = *pos;
+
+#if (NGX_HAVE_NONALIGNED)
+    if (*(uint32_t *) p != NGX_SPDY_SETTINGS_HEAD) {
+#else
+    if (p[0] != NGX_SPDY_CTRL_BYTE
+        || p[1] != NGX_SPDY_VERSION
+        || p[2] != 0x00
+        || p[3] != NGX_SPDY_SETTINGS)
+    {
+#endif
+        ngx_http_spdy_send_settings(sc);
+
+        sc->handler = ngx_http_spdy_process_frame;
+        return NGX_OK;
+    }
+
+    sc->length = ngx_spdy_frame_parse_len(p + 5);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+                   "spdy SETTINGS frame received, size: %ui",
+                   sc->length);
+
+    *pos += NGX_SPDY_HEADER_SIZE;
+
+    sc->handler = ngx_http_spdy_process_settings_frame;
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_spdy_process_settings_frame(ngx_http_spdy_connection_t *sc,
+    u_char **pos, size_t size)
+{
+    u_char                    *p;
+    ngx_uint_t                 v;
+    ngx_http_spdy_srv_conf_t  *sscf;
+
+    if (sc->headers == 0) {
+
+        if (size < 4) {
+            return NGX_AGAIN;
+        }
+
+        sc->headers = ngx_spdy_frame_parse_uint32(*pos);
+
+        *pos += 4;
+        size -= 4;
+        sc->length -= 4;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+                       "spdy SETTINGS frame consists of %ui entries",
+                       sc->headers);
+    }
+
+    p = *pos;
+
+    do {
+        if (size < 8) {
+            *pos = p;
+            return NGX_AGAIN;
+        }
+
+        if (p[0] != 0x04) {
+            p += 8;
+            size -= 8;
+            sc->length -= 8;
+            continue;
+        }
+
+        v = ngx_spdy_frame_parse_uint32(p + 4);
+
+        sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx,
+                                            ngx_http_spdy_module);
+
+        if (v != sscf->concurrent_streams) {
+            ngx_http_spdy_send_settings(sc);
+        }
+
+        sc->handler = ngx_http_spdy_skip_frame;
+        return NGX_OK;
+
+    } while (--sc->headers);
+
+    *pos = p;
+
+    ngx_http_spdy_send_settings(sc);
+
+    sc->handler = ngx_http_spdy_process_frame;
+    return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_spdy_skip_frame(ngx_http_spdy_connection_t *sc, u_char **pos,
+    size_t size)
+{
+    if (size < sc->length) {
+        *pos += size;
+        sc->length -= size;
+        return NGX_AGAIN;
+    }
+
+    *pos += sc->length;
+    sc->handler = ngx_http_spdy_process_frame;
+
+    return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_spdy_process_syn_stream(ngx_http_spdy_connection_t *sc, u_char **pos,
+    size_t size)
+{
+    u_char                    *p;
+    ngx_uint_t                 sid, prio;
+    ngx_http_spdy_stream_t    *stream;
+    ngx_http_spdy_srv_conf_t  *sscf;
+
+    if (size < 10) {
+        return NGX_AGAIN;
+    }
+
+    p = *pos;
+
+    sc->length -= 10;
+    *pos += 10;
+
+    sid = ngx_spdy_frame_parse_sid(p);
+    prio = p[8] >> 6;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+                   "spdy SYN_STREAM frame sid:%ui prio:%ui", sid, prio);
+
+    sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx,
+                                        ngx_http_spdy_module);
+
+    if (sc->processing >= sscf->concurrent_streams) {
+
+        ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0,
+                      "spdy concurrent streams excessed %ui", sc->processing);
+
+        ngx_http_spdy_send_rst_stream(sc, sid, NGX_SPDY_REFUSED_STREAM, prio);
+
+        sc->handler = ngx_http_spdy_skip_headers;
+        return NGX_OK;
+    }
+
+    stream = ngx_http_spdy_create_stream(sc, sid, prio);
+    if (stream == NULL) {
+        return NGX_ERROR;
+    }
+
+    stream->half_closed = (sc->flags & NGX_SPDY_FLAG_FIN) ? 1 : 0;
+
+    stream->request->request_length = 18 + sc->length;
+
+    sc->stream = stream;
+    sc->handler = ngx_http_spdy_process_headers;
+
+    sc->last_sid = sid;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_spdy_process_data_frame(ngx_http_spdy_connection_t *sc, u_char **pos,
+    size_t size)
+{
+    u_char                   *p;
+    ssize_t                   n;
+    ngx_buf_t                *buf;
+    ngx_uint_t                complete;
+    ngx_temp_file_t          *tf;
+    ngx_http_request_t       *r;
+    ngx_http_request_body_t  *rb;
+
+    if (size >= sc->length) {
+        complete = 1;
+        size = sc->length;
+
+    } else {
+        complete = 0;
+        sc->length -= size;
+    }
+
+    r = sc->stream->request;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+                   "spdy DATA frame");
+
+    if (!r->request_body) {
+        if (ngx_http_spdy_init_request_body(r) != NGX_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+    rb = r->request_body;
+    tf = rb->temp_file;
+    buf = rb->buf;
+
+    if (size) {
+        if ((off_t) size > rb->rest) {
+            return NGX_ERROR;
+        }
+
+        rb->rest -= size;
+        p = *pos;
+
+        if (tf) {
+            buf->start = p;
+            buf->pos = p;
+
+            p += size;
+
+            buf->end = p;
+            buf->last = p;
+
+            n = ngx_write_chain_to_temp_file(tf, rb->bufs);
+
+            /* TODO: n == 0 or not complete and level event */
+
+            if (n == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            tf->offset += n;
+
+        } else {
+            buf->last = ngx_cpymem(buf->last, p, size);
+            p += size;
+        }
+
+        *pos = p;
+    }
+
+    if (!complete) {
+        return NGX_AGAIN;
+    }
+
+    sc->handler = ngx_http_spdy_process_frame;
+
+    if (sc->flags & NGX_SPDY_FLAG_FIN) {
+
+        sc->stream->half_closed = 1;
+
+        if (tf) {
+            ngx_memzero(buf, sizeof(ngx_buf_t));
+
+            buf->in_file = 1;
+            buf->file_last = tf->file.offset;
+            buf->file = &tf->file;
+
+            rb->buf = NULL;
+        }
+
+        if (rb->post_handler) {
+            rb->post_handler(r);
+        }
+    }
+
+    return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_spdy_init_request_body(ngx_http_request_t *r)
+{
+    ngx_buf_t                 *buf;
+    ngx_temp_file_t           *tf;
+    ngx_http_request_body_t   *rb;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t));
+    if (rb == NULL) {
+        return NGX_ERROR;
+    }
+
+    r->request_body = rb;
+
+    if (r->headers_in.content_length_n < 0) {
+        return NGX_OK;
+    }
+
+    rb->rest = r->headers_in.content_length_n;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (r->request_body_in_file_only
+        || rb->rest > (off_t) clcf->client_body_buffer_size)
+    {
+        tf = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t));
+        if (tf == NULL) {
+            return NGX_ERROR;
+        }
+
+        tf->file.fd = NGX_INVALID_FILE;
+        tf->file.log = r->connection->log;
+        tf->path = clcf->client_body_temp_path;
+        tf->pool = r->pool;
+        tf->warn = "a client request body is buffered to a temporary file";
+        tf->log_level = r->request_body_file_log_level;
+        tf->persistent = r->request_body_in_persistent_file;
+        tf->clean = r->request_body_in_clean_file;
+
+        if (r->request_body_file_group_access) {
+            tf->access = 0660;
+        }
+
+        rb->temp_file = tf;
+
+        if (r->spdy_stream->half_closed
+            && ngx_create_temp_file(&tf->file, tf->path, tf->pool,
+                                    tf->persistent, tf->clean, tf->access)
+               != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+
+        buf = ngx_calloc_buf(r->pool);
+        if (buf == NULL) {
+            return NGX_ERROR;
+        }
+
+        if (rb->rest == 0) {
+            buf->in_file = 1;
+            buf->file = &tf->file;
+        } else {
+            rb->buf = buf;
+        }
+
+    } else {
+
+        if (rb->rest == 0) {
+            return NGX_OK;
+        }
+
+        buf = ngx_create_temp_buf(r->pool, (size_t) rb->rest);
+        if (buf == NULL) {
+            return NGX_ERROR;
+        }
+
+        rb->buf = buf;
+    }
+
+    rb->bufs = ngx_alloc_chain_link(r->pool);
+    if (rb->bufs == NULL) {
+        return NGX_ERROR;
+    }
+
+    rb->bufs->buf = buf;
+    rb->bufs->next = NULL;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_spdy_process_rst_stream(ngx_http_spdy_connection_t *sc, u_char **pos,
+    size_t size)
+{
+    u_char                  *p;
+    ngx_uint_t               sid, status;
+    ngx_event_t             *ev;
+    ngx_connection_t        *fc;
+    ngx_http_request_t      *r;
+    ngx_http_spdy_stream_t  *stream;
+
+    if (size < 8) {
+        return NGX_AGAIN;
+    }
+
+    p = *pos;
+
+    if (sc->length != 8 || sc->flags) {
+        return NGX_ERROR;
+    }
+
+    sid = ngx_spdy_frame_parse_sid(p);
+    status = p[7];
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+                   "spdy RST_STREAM sid:%ui st:%ui", sid, status);
+
+
+    switch (status) {
+
+    case NGX_SPDY_PROTOCOL_ERROR:
+        /* TODO */
+        break;
+
+    case NGX_SPDY_INVALID_STREAM:
+        /* TODO */
+        break;
+
+    case NGX_SPDY_REFUSED_STREAM:
+        /* TODO */
+        break;
+
+    case NGX_SPDY_UNSUPPORTED_VERSION:
+        /* TODO */
+        break;
+
+    case NGX_SPDY_CANCEL:
+        stream = ngx_http_spdy_get_stream_by_id(sc, sid);
+        if (stream == NULL) {
+            /* TODO false cancel */
+            break;
+        }
+
+        r = stream->request;
+
+        fc = r->connection;
+        fc->error = 1;
+
+        ev = fc->read;
+
+        ev->handler(ev);
+        break;
+
+    case NGX_SPDY_INTERNAL_ERROR:
+        /* TODO */
+        break;
+
+    case NGX_SPDY_FLOW_CONTROL_ERROR:
+        /* TODO */
+        break;
+
+    default:
+        return NGX_ERROR;
+    }
+
+    *pos += 8;
+    sc->handler = ngx_http_spdy_process_frame;
+
+    return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_spdy_process_ping(ngx_http_spdy_connection_t *sc, u_char **pos,
+    size_t size)
+{
+    u_char                     *p, *d;
+    ngx_buf_t                  *buf;
+    ngx_http_spdy_out_frame_t  *frame;
+
+    static u_char ping_header[NGX_SPDY_HEADER_SIZE] = {
+        NGX_SPDY_CTRL_BYTE, NGX_SPDY_VERSION, 0x00, NGX_SPDY_PING,
+        0x00, 0x00, 0x00, NGX_SPDY_PING_SIZE
+    };
+
+    if (size < NGX_SPDY_PING_SIZE) {
+        return NGX_AGAIN;
+    }
+
+    if (sc->length != NGX_SPDY_PING_SIZE) {
+        return NGX_ERROR;
+    }
+
+    p = *pos;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+                   "spdy PING frame");
+
+    frame = ngx_http_spdy_get_ctrl_frame(sc, NGX_SPDY_PING_SIZE,
+                                         NGX_SPDY_HIGHEST_PRIORITY);
+    if (frame == NULL) {
+        return NGX_ERROR;
+    }
+
+    buf = frame->first->buf;
+
+    d = buf->pos;
+    d = ngx_cpymem(d, ping_header, NGX_SPDY_HEADER_SIZE);
+    d = ngx_cpymem(d, p, NGX_SPDY_PING_SIZE);
+
+    buf->last = d;
+
+    ngx_http_spdy_queue_frame(sc, frame);
+
+    sc->handler = ngx_http_spdy_process_frame;
+
+    *pos += NGX_SPDY_PING_SIZE;
+
+    return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_spdy_process_headers(ngx_http_spdy_connection_t *sc, u_char **pos,
+    size_t size)
+{
+    int                  z;
+    ngx_buf_t           *buf;
+    ngx_int_t            rc;
+    ngx_uint_t           last;
+    ngx_connection_t    *c;
+    ngx_http_request_t  *r;
+
+    c = sc->connection;
+    r = sc->stream->request;
+    buf = r->header_in;
+
+    if (size >= sc->length) {
+        last = 1;
+        size = sc->length;
+
+    } else {
+        last = 0;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "spdy process headers %d of %d", size, sc->length);
+
+    sc->zstream_in.next_in = *pos;
+    sc->zstream_in.avail_in = size;
+    sc->zstream_in.next_out = buf->last;
+    sc->zstream_in.avail_out = buf->end - buf->last - 1;
+
+    z = inflate(&sc->zstream_in, Z_NO_FLUSH);
+
+    if (z == Z_NEED_DICT) {
+        z = inflateSetDictionary(&sc->zstream_in, ngx_http_spdy_dict,
+                                 sizeof(ngx_http_spdy_dict));
+        if (z != Z_OK) {
+            ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                          "spdy inflateSetDictionary() failed: %d", z);
+            return NGX_ERROR;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "spdy inflateSetDictionary(): %d", z);
+
+        z  = sc->zstream_in.avail_in ? inflate(&sc->zstream_in, Z_NO_FLUSH)
+                                     : Z_OK;
+    }
+
+    if (z != Z_OK) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                      "spdy inflate() failed: %d", z);
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "spdy inflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d",
+                   sc->zstream_in.next_in, sc->zstream_in.next_out,
+                   sc->zstream_in.avail_in, sc->zstream_in.avail_out,
+                   z);
+
+    *pos = sc->zstream_in.next_in;
+
+    sc->length -= (size - sc->zstream_in.avail_in);
+    size = sc->zstream_in.avail_in;
+
+    buf->last = sc->zstream_in.next_out;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "spdy headers decompressed: \"%*s\"",
+                   buf->last - buf->pos, buf->pos);
+
+    if (r->headers_in.headers.part.elts == NULL) {
+
+        if (buf->last - buf->pos < 2) {
+            return NGX_AGAIN;
+        }
+
+        sc->headers = ngx_spdy_frame_parse_uint16(buf->pos);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "spdy headers count: %i", sc->headers);
+
+        buf->pos += 2;
+
+        if (ngx_list_init(&r->headers_in.headers, r->pool, sc->headers + 3,
+                          sizeof(ngx_table_elt_t))
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+
+        if (ngx_array_init(&r->headers_in.cookies, r->pool, 2,
+                           sizeof(ngx_table_elt_t *))
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+    }
+
+    while (sc->headers) {
+
+        rc = ngx_http_spdy_parse_header(r);
+
+        switch (rc) {
+
+        case NGX_DONE:
+            sc->headers--;
+
+        case NGX_OK:
+            break;
+
+        case NGX_AGAIN:
+
+            if (sc->zstream_in.avail_in) {
+
+                rc = ngx_http_spdy_alloc_large_header_buffer(r);
+
+                if (rc == NGX_DECLINED) {
+                    /* TODO logging */
+                    ngx_http_finalize_request(r,
+                                            NGX_HTTP_REQUEST_HEADER_TOO_LARGE);
+
+                    sc->handler = ngx_http_spdy_skip_headers;
+                    return NGX_OK;
+                }
+
+                if (rc != NGX_OK) {
+                    return NGX_ERROR;
+                }
+
+                *buf->pos = '\0';
+
+                buf = r->header_in;
+
+                sc->zstream_in.next_out = buf->last;
+                sc->zstream_in.avail_out = buf->end - buf->last - 1;
+
+                z = inflate(&sc->zstream_in, Z_NO_FLUSH);
+
+                if (z != Z_OK) {
+                    ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                                  "spdy inflate() failed: %d", z);
+                    return NGX_ERROR;
+                }
+
+                *pos = sc->zstream_in.next_in;
+
+                buf->last = sc->zstream_in.next_out;
+
+                sc->length -= (size - sc->zstream_in.avail_in);
+                size = sc->zstream_in.avail_in;
+
+                continue;
+            }
+
+            if (last) {
+                ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                               "spdy again while last");
+                return NGX_ERROR;
+            }
+
+            return NGX_AGAIN;
+
+        default:
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "NGX_ERROR");
+            return NGX_ERROR;
+        }
+
+        /* a header line has been parsed successfully */
+
+        if (ngx_http_spdy_handle_request_headers(r) != NGX_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+    if (!last) {
+        return NGX_AGAIN;
+    }
+
+    if (buf->pos != buf->last) {
+        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "end %i %d %d", last, buf->pos, buf->last);
+        return NGX_ERROR;
+    }
+
+    *buf->pos = '\0';
+
+    ngx_http_spdy_run_request(r);
+
+    sc->handler = ngx_http_spdy_process_frame;
+
+    return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_spdy_alloc_large_header_buffer(ngx_http_request_t *r)
+{
+    u_char                    *old, *new;
+    size_t                     rest;
+    ngx_buf_t                 *buf;
+    ngx_http_spdy_stream_t    *stream;
+    ngx_http_core_srv_conf_t  *cscf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "spdy alloc large header buffer");
+
+    stream = r->spdy_stream;
+
+    cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+    if (stream->header_buffers
+        == (ngx_uint_t) cscf->large_client_header_buffers.num)
+    {
+        return NGX_DECLINED;
+    }
+
+    rest = r->header_in->last - r->header_in->pos;
+
+    if (rest >= cscf->large_client_header_buffers.size) {
+        return NGX_DECLINED;
+    }
+
+    buf = ngx_create_temp_buf(r->pool, cscf->large_client_header_buffers.size);
+    if (buf == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "spdy large header alloc: %p %uz",
+                   buf->pos, buf->end - buf->last);
+
+    old = r->header_in->pos;
+    new = buf->pos;
+
+    if (rest) {
+        buf->last = ngx_cpymem(new, old, rest);
+    }
+
+    if (r->header_name_end > old) {
+        r->header_name_end = new + (r->header_name_end - old);
+
+    } else if (r->header_end > old) {
+        r->header_end = new + (r->header_end - old);
+    }
+
+    r->header_in = buf;
+
+    stream->header_buffers++;
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_spdy_run_request(ngx_http_request_t *r)
+{
+    ngx_uint_t                  i;
+    ngx_list_part_t            *part;
+    ngx_table_elt_t            *h;
+    ngx_http_header_t          *hh;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    if (ngx_http_spdy_construct_request_line(r) != NGX_OK) {
+        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "spdy http request line: \"%V\"", &r->request_line);
+
+    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+    part = &r->headers_in.headers.part;
+    h = part->elts;
+
+    for (i = 0 ;; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            h = part->elts;
+            i = 0;
+        }
+
+        hh = ngx_hash_find(&cmcf->headers_in_hash, h[i].hash,
+                           h[i].lowcase_key, h[i].key.len);
+
+        if (hh && hh->handler(r, &h[i], hh->offset) != NGX_OK) {
+            return;
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http header: \"%V: %V\"", &h[i].key, &h[i].value);
+    }
+
+    r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;
+
+    if (ngx_http_process_request_header(r) != NGX_OK) {
+        return;
+    }
+
+    ngx_http_process_request(r);
+}
+
+
+static ngx_int_t
+ngx_http_spdy_skip_headers(ngx_http_spdy_connection_t *sc, u_char **pos,
+    size_t size)
+{
+    int     n;
+    u_char  buffer[NGX_SPDY_SKIP_HEADERS_BUFFER_SIZE];
+
+    sc->zstream_in.next_in = *pos;
+    sc->zstream_in.avail_in = (size < sc->length) ? size : sc->length;
+
+    while (sc->zstream_in.avail_in) {
+        sc->zstream_in.next_out = buffer;
+        sc->zstream_in.avail_out = NGX_SPDY_SKIP_HEADERS_BUFFER_SIZE;
+
+        n = inflate(&sc->zstream_in, Z_NO_FLUSH);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+                       "spdy inflate(): %d", n);
+
+        if (n != Z_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+    *pos = sc->zstream_in.next_in;
+
+    if (size < sc->length) {
+        sc->length -= size;
+        return NGX_AGAIN;
+    }
+
+    sc->handler = ngx_http_spdy_process_frame;
+
+    return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_spdy_parse_header(ngx_http_request_t *r)
+{
+    u_char                     *p, *end, ch;
+    ngx_uint_t                  len, hash;
+    ngx_http_core_srv_conf_t   *cscf;
+
+    enum {
+        sw_name_len = 0,
+        sw_name,
+        sw_value_len,
+        sw_value
+    } state;
+
+    state = r->state;
+
+    p = r->header_in->pos;
+    end = r->header_in->last;
+
+    switch (state) {
+
+    case sw_name_len:
+
+        if (end - p < 2) {
+            return NGX_AGAIN;
+        }
+
+        len = ngx_spdy_frame_parse_uint16(p);
+
+        if (!len) {
+            return NGX_ERROR;
+        }
+
+        *p = '\0';
+
+        p += 2;
+
+        r->header_name_end = p + len;
+        r->lowcase_index = len;
+        r->invalid_header = 0;
+
+        state = sw_name;
+
+        /* fall through */
+
+    case sw_name:
+
+        if (r->header_name_end > end) {
+            break;
+        }
+
+        cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+        r->header_name_start = p;
+
+        hash = 0;
+
+        for ( /* void */ ; p != r->header_name_end; p++) {
+
+            ch = *p;
+
+            hash = ngx_hash(hash, ch);
+
+            if ((ch >= 'a' && ch <= 'z')
+                || (ch == '-')
+                || (ch >= '0' && ch <= '9')
+                || (ch == '_' && cscf->underscores_in_headers))
+            {
+                continue;
+            }
+
+            switch (ch) {
+            case '\0':
+            case LF:
+            case CR:
+            case ':':
+                return NGX_ERROR;
+            }
+
+            r->invalid_header = 1;
+        }
+
+        r->header_hash = hash;
+
+        state = sw_value_len;
+
+        /* fall through */
+
+    case sw_value_len:
+
+        if (end - p < 2) {
+            break;
+        }
+
+        len = ngx_spdy_frame_parse_uint16(p);
+
+        if (!len) {
+            return NGX_ERROR;
+        }
+
+        *p = '\0';
+
+        p += 2;
+
+        r->header_end = p + len;
+
+        state = sw_value;
+
+        /* fall through */
+
+    case sw_value:
+
+        if (r->header_end > end) {
+            break;
+        }
+
+        r->header_start = p;
+
+        for ( /* void */ ; p != r->header_end; p++) {
+
+            ch = *p;
+
+            if (ch == '\0') {
+
+                if (p == r->header_start) {
+                    return NGX_ERROR;
+                }
+
+                r->header_size = p - r->header_start;
+                r->header_in->pos = p + 1;
+
+                return NGX_OK;
+            }
+
+            if (ch == CR || ch == LF) {
+                return NGX_ERROR;
+            }
+        }
+
+        r->header_size = p - r->header_start;
+        r->header_in->pos = p;
+
+        r->state = 0;
+
+        return NGX_DONE;
+    }
+
+    r->header_in->pos = p;
+    r->state = state;
+
+    return NGX_AGAIN;
+}
+
+
+static ngx_int_t
+ngx_http_spdy_handle_request_headers(ngx_http_request_t *r)
+{
+    ngx_uint_t                       i;
+    ngx_table_elt_t                 *h;
+    ngx_http_core_srv_conf_t        *cscf;
+    ngx_http_spdy_request_header_t  *sh;
+
+    if (r->invalid_header) {
+        cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+        if (cscf->ignore_invalid_headers) {
+            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                          "client sent invalid header: \"%*s\"",
+                          r->header_end - r->header_name_start,
+                          r->header_name_start);
+            return NGX_OK;
+        }
+
+    } else {
+        for (i = 0; i < NGX_SPDY_REQUEST_HEADERS; i++) {
+            sh = &ngx_http_spdy_request_headers[i];
+
+            if (sh->hash != r->header_hash
+                || sh->header.len != r->lowcase_index
+                || ngx_strncmp(sh->header.data, r->header_name_start,
+                               r->lowcase_index)
+                   != 0)
+            {
+                continue;
+            }
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "spdy request header: \"%V\"", &sh->header);
+
+            return sh->handler(r);
+        }
+    }
+
+    h = ngx_list_push(&r->headers_in.headers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    h->hash = r->header_hash;
+
+    h->key.len = r->lowcase_index;
+    h->key.data = r->header_name_start;
+
+    h->value.len = r->header_size;
+    h->value.data = r->header_start;
+
+    h->lowcase_key = h->key.data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_spdy_parse_method(ngx_http_request_t *r)
+{
+    u_char                       *p, *m;
+    size_t                        k, len;
+    ngx_uint_t                    n;
+    ngx_http_spdy_method_test_t  *test;
+
+    /*
+     * This array takes less than 256 sequential bytes,
+     * and if typical CPU cache line size is 64 bytes,
+     * it is prefetched for 4 load operations.
+     */
+    static ngx_http_spdy_method_test_t  tests[]  ngx_aligned(64) = {
+        { 3, "GET",       NGX_HTTP_GET },
+        { 4, "POST",      NGX_HTTP_POST },
+        { 4, "HEAD",      NGX_HTTP_HEAD },
+        { 7, "OPTIONS",   NGX_HTTP_OPTIONS },
+        { 8, "PROPFIND",  NGX_HTTP_PROPFIND },
+        { 3, "PUT",       NGX_HTTP_PUT },
+        { 5, "MKCOL",     NGX_HTTP_MKCOL },
+        { 6, "DELETE",    NGX_HTTP_DELETE },
+        { 4, "COPY",      NGX_HTTP_COPY },
+        { 4, "MOVE",      NGX_HTTP_MOVE },
+        { 9, "PROPPATCH", NGX_HTTP_PROPPATCH },
+        { 4, "LOCK",      NGX_HTTP_LOCK },
+        { 6, "UNLOCK",    NGX_HTTP_UNLOCK },
+        { 5, "PATCH",     NGX_HTTP_PATCH },
+        { 5, "TRACE",     NGX_HTTP_TRACE }
+    };
+
+    if (r->method_name.len) {
+        return NGX_ERROR;
+    }
+
+    len = r->header_size;
+
+    r->method_name.len = len;
+    r->method_name.data = r->header_start;
+
+    test = tests;
+    n = sizeof(tests) / sizeof(ngx_http_spdy_method_test_t);
+
+    do {
+        if (len == test->len) {
+            p = r->method_name.data;
+            m = test->method;
+            k = len;
+
+            do {
+                if (*p++ != *m++) {
+                    goto next;
+                }
+            } while (--k);
+
+            r->method = test->value;
+            return NGX_OK;
+        }
+
+    next:
+        test++;
+
+    } while (--n);
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_spdy_parse_scheme(ngx_http_request_t *r)
+{
+    if (r->schema_start) {
+        return NGX_ERROR;
+    }
+
+    r->schema_start = r->header_start;
+    r->schema_end = r->header_end;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_spdy_parse_url(ngx_http_request_t *r)
+{
+    ngx_http_core_srv_conf_t  *cscf;
+
+    if (r->unparsed_uri.len) {
+        return NGX_ERROR;
+    }
+
+    r->uri_start = r->header_start;
+    r->uri_end = r->header_end;
+
+    if (ngx_http_parse_uri(r) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (r->args_start) {
+        r->uri.len = r->args_start - 1 - r->uri_start;
+    } else {
+        r->uri.len = r->header_size;
+    }
+
+    if (r->complex_uri || r->quoted_uri) {
+
+        r->uri.data = ngx_pnalloc(r->pool, r->uri.len + 1);
+        if (r->uri.data == NULL) {
+            return NGX_ERROR;
+        }
+
+        cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+        if (ngx_http_parse_complex_uri(r, cscf->merge_slashes) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+    } else {
+        r->uri.data = r->uri_start;
+    }
+
+    r->unparsed_uri.len = r->header_size;
+    r->unparsed_uri.data = r->uri_start;
+
+    r->valid_unparsed_uri = r->space_in_uri ? 0 : 1;
+
+    if (r->uri_ext) {
+        if (r->args_start) {
+            r->exten.len = r->args_start - 1 - r->uri_ext;
+        } else {
+            r->exten.len = r->uri_end - r->uri_ext;
+        }
+
+        r->exten.data = r->uri_ext;
+    }
+
+    if (r->args_start && r->uri_end > r->args_start) {
+        r->args.len = r->uri_end - r->args_start;
+        r->args.data = r->args_start;
+    }
+
+#if (NGX_WIN32)
+    {
+    u_char  *p, *last;
+
+    p = r->uri.data;
+    last = r->uri.data + r->uri.len;
+
+    while (p < last) {
+
+        if (*p++ == ':') {
+
+            /*
+             * this check covers "::$data", "::$index_allocation" and
+             * ":$i30:$index_allocation"
+             */
+
+            if (p < last && *p == '$') {
+                ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                              "client sent unsafe win32 URI");
+                return NGX_ERROR; /* FIXME bad request */
+            }
+        }
+    }
+
+    p = r->uri.data + r->uri.len - 1;
+
+    while (p > r->uri.data) {
+
+        if (*p == ' ') {
+            p--;
+            continue;
+        }
+
+        if (*p == '.') {
+            p--;
+            continue;
+        }
+
+        break;
+    }
+
+    if (p != r->uri.data + r->uri.len - 1) {
+        r->uri.len = p + 1 - r->uri.data;
+        ngx_http_set_exten(r);
+    }
+
+    }
+#endif
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http uri: \"%V\"", &r->uri);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http args: \"%V\"", &r->args);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http exten: \"%V\"", &r->exten);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_spdy_parse_version(ngx_http_request_t *r)
+{
+    u_char  *p, ch;
+
+    if (r->http_protocol.len) {
+        return NGX_ERROR;
+    }
+
+    p = r->header_start;
+
+    if (r->header_size < 8 || !(ngx_str5cmp(p, 'H', 'T', 'T', 'P', '/'))) {
+        return NGX_ERROR;
+    }
+
+    ch = *(p + 5);
+
+    if (ch < '1' || ch > '9') {
+        return NGX_ERROR;
+    }
+
+    r->http_major = ch - '0';
+
+    for (p += 6; p != r->header_end - 2; p++) {
+
+        ch = *p;
+
+        if (ch < '0' || ch > '9') {
+            return NGX_ERROR;
+        }
+
+        r->http_major = r->http_major * 10 + ch - '0';
+    }
+
+    if (*p != '.') {
+        return NGX_ERROR;
+    }
+
+    ch = *(p + 1);
+
+    if (ch < '0' || ch > '9') {
+        return NGX_ERROR;
+    }
+
+    r->http_minor = ch - '0';
+
+    for (p += 2; p != r->header_end; p++) {
+
+        ch = *p;
+
+        if (ch < '0' || ch > '9') {
+            return NGX_ERROR;
+        }
+
+        r->http_minor = r->http_minor * 10 + ch - '0';
+    }
+
+    r->http_protocol.len = r->header_size;
+    r->http_protocol.data = r->header_start;
+    r->http_version = r->http_major * 1000 + r->http_minor;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_spdy_construct_request_line(ngx_http_request_t *r)
+{
+    u_char  *p;
+
+    if (r->method_name.len == 0
+        || r->unparsed_uri.len == 0
+        || r->http_protocol.len == 0)
+    {
+        return NGX_ERROR;
+    }
+
+    r->request_line.len = r->method_name.len + 1
+                          + r->unparsed_uri.len + 1
+                          + r->http_protocol.len;
+
+    p = ngx_pnalloc(r->pool, r->request_line.len + 1);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    r->request_line.data = p;
+
+    p = ngx_cpymem(p, r->method_name.data, r->method_name.len);
+
+    *p++ = ' ';
+
+    p = ngx_cpymem(p, r->unparsed_uri.data, r->unparsed_uri.len);
+
+    *p++ = ' ';
+
+    ngx_memcpy(p, r->http_protocol.data, r->http_protocol.len + 1);
+
+    /* some modules expect the space character after method name */
+    r->method_name.data = r->request_line.data;
+
+    return NGX_OK;
+}
+
+
+static ngx_http_spdy_out_frame_t *
+ngx_http_spdy_get_ctrl_frame(ngx_http_spdy_connection_t *sc, size_t size,
+    ngx_uint_t priority)
+{
+    ngx_chain_t                *cl;
+    ngx_http_spdy_out_frame_t  *frame;
+
+    frame = sc->free_ctrl_frames;
+
+    if (frame) {
+        sc->free_ctrl_frames = frame->free;
+
+        cl = frame->first;
+        cl->buf->pos = cl->buf->start;
+
+    } else {
+        frame = ngx_palloc(sc->pool, sizeof(ngx_http_spdy_out_frame_t));
+        if (frame == NULL) {
+            return NULL;
+        }
+
+        cl = ngx_alloc_chain_link(sc->pool);
+        if (cl == NULL) {
+            return NULL;
+        }
+
+        cl->buf = ngx_create_temp_buf(sc->pool,
+                                      NGX_SPDY_CTRL_FRAME_BUFFER_SIZE);
+        if (cl->buf == NULL) {
+            return NULL;
+        }
+
+        cl->buf->last_buf = 1;
+
+        frame->first = cl;
+        frame->last = cl;
+        frame->handler = ngx_http_spdy_ctrl_frame_handler;
+    }
+
+    frame->free = NULL;
+
+#if (NGX_DEBUG)
+    if (size > NGX_SPDY_CTRL_FRAME_BUFFER_SIZE - NGX_SPDY_HEADER_SIZE) {
+        ngx_log_error(NGX_LOG_ALERT, sc->pool->log, 0,
+                      "requested control frame is too big: %z", size);
+        return NULL;
+    }
+
+    frame->stream = NULL;
+    frame->size = size;
+#endif
+
+    frame->blocked = 0;
+    frame->priority = priority;
+
+    return frame;
+}
+
+
+static ngx_int_t
+ngx_http_spdy_ctrl_frame_handler(ngx_http_spdy_connection_t *sc,
+    ngx_http_spdy_out_frame_t *frame)
+{
+    ngx_buf_t  *buf;
+
+    buf = frame->first->buf;
+
+    if (buf->pos != buf->last) {
+        return NGX_AGAIN;
+    }
+
+    frame->free = sc->free_ctrl_frames;
+    sc->free_ctrl_frames = frame;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_spdy_send_rst_stream(ngx_http_spdy_connection_t *sc, ngx_uint_t sid,
+    ngx_uint_t status, ngx_uint_t priority)
+{
+    u_char                     *p;
+    ngx_buf_t                  *buf;
+    ngx_http_spdy_out_frame_t  *frame;
+
+    static u_char rst_stream_header[NGX_SPDY_HEADER_SIZE] = {
+        NGX_SPDY_CTRL_BYTE, NGX_SPDY_VERSION, 0x00, NGX_SPDY_RST_STREAM,
+        0x00, 0x00, 0x00, NGX_SPDY_RST_STREAM_SIZE
+    };
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+                   "spdy write RST_STREAM sid:%ui st:%ui", sid, status);
+
+    frame = ngx_http_spdy_get_ctrl_frame(sc, NGX_SPDY_RST_STREAM_SIZE,
+                                         priority);
+    if (frame == NULL) {
+        return NGX_ERROR;
+    }
+
+    buf = frame->first->buf;
+
+    p = buf->pos;
+    p = ngx_cpymem(p, rst_stream_header, NGX_SPDY_HEADER_SIZE);
+
+    p = ngx_spdy_frame_aligned_write_uint32(p, sid);
+    p = ngx_spdy_frame_aligned_write_uint32(p, status);
+
+    buf->last = p;
+
+    ngx_http_spdy_queue_frame(sc, frame);
+
+    return NGX_OK;
+}
+
+
+#if 0
+static ngx_int_t
+ngx_http_spdy_send_goaway(ngx_http_spdy_connection_t *sc)
+{
+    u_char                     *p;
+    ngx_buf_t                  *buf;
+    ngx_http_spdy_out_frame_t  *frame;
+
+    static u_char goaway_header[NGX_SPDY_HEADER_SIZE] = {
+        NGX_SPDY_CTRL_BYTE, NGX_SPDY_VERSION, 0x00, NGX_SPDY_GOAWAY,
+        0x00, 0x00, 0x00, NGX_SPDY_GOAWAY_SIZE
+    };
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+                   "spdy create GOAWAY sid:%ui", sc->last_sid);
+
+    frame = ngx_http_spdy_get_ctrl_frame(sc, NGX_SPDY_GOAWAY_SIZE,
+                                         NGX_SPDY_HIGHEST_PRIORITY);
+    if (frame == NULL) {
+        return NGX_ERROR;
+    }
+
+    buf = frame->first->buf;
+
+    p = buf->pos;
+    p = ngx_cpymem(p, goaway_header, NGX_SPDY_HEADER_SIZE);
+
+    p = ngx_spdy_frame_aligned_write_uint32(p, sc->last_sid);
+
+    buf->last = p;
+
+    ngx_http_spdy_queue_frame(sc, frame);
+
+    return NGX_OK;
+}
+#endif
+
+
+static ngx_int_t
+ngx_http_spdy_send_settings(ngx_http_spdy_connection_t *sc)
+{
+    u_char                     *p;
+    ngx_buf_t                  *buf;
+    ngx_pool_t                 *pool;
+    ngx_chain_t                *cl;
+    ngx_http_spdy_srv_conf_t   *sscf;
+    ngx_http_spdy_out_frame_t  *frame;
+
+    static u_char settings_header[NGX_SPDY_HEADER_SIZE] = {
+        NGX_SPDY_CTRL_BYTE, NGX_SPDY_VERSION, 0x00, NGX_SPDY_SETTINGS,
+        NGX_SPDY_FLAG_CLEAR_SETTINGS, 0x00, 0x00, 0x0c
+    };
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+                   "spdy create SETTINGS frame");
+
+    pool = sc->connection->pool;
+
+    frame = ngx_palloc(pool, sizeof(ngx_http_spdy_out_frame_t));
+    if (frame == NULL) {
+        return NGX_ERROR;
+    }
+
+    cl = ngx_alloc_chain_link(pool);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    buf = ngx_create_temp_buf(pool, 20);
+    if (buf == NULL) {
+        return NGX_ERROR;
+    }
+
+    buf->last_buf = 1;
+
+    cl->buf = buf;
+    cl->next = NULL;
+
+    frame->first = cl;
+    frame->last = cl;
+    frame->handler = ngx_http_spdy_settings_frame_handler;
+#if (NGX_DEBUG)
+    frame->stream = NULL;
+    frame->size = 20;
+#endif
+    frame->blocked = 0;
+    frame->priority = NGX_SPDY_HIGHEST_PRIORITY;
+
+    p = buf->pos;
+
+    p = ngx_cpymem(p, settings_header, NGX_SPDY_HEADER_SIZE);
+
+    p = ngx_spdy_frame_aligned_write_uint32(p, 1);
+
+    p = ngx_spdy_frame_aligned_write_uint32(p, 0x04000001);
+
+    sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx,
+                                        ngx_http_spdy_module);
+
+    p = ngx_spdy_frame_aligned_write_uint32(p, sscf->concurrent_streams);
+
+    buf->last = p;
+
+    ngx_http_spdy_queue_frame(sc, frame);
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_spdy_settings_frame_handler(ngx_http_spdy_connection_t *sc,
+    ngx_http_spdy_out_frame_t *frame)
+{
+    ngx_buf_t  *buf;
+
+    buf = frame->first->buf;
+
+    if (buf->pos != buf->last) {
+        return NGX_AGAIN;
+    }
+
+    ngx_free_chain(sc->pool, frame->first);
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_spdy_handle_connection_handler(ngx_event_t *rev)
+{
+    ngx_connection_t  *c;
+
+    rev->handler = ngx_http_spdy_read_handler;
+
+    if (rev->ready) {
+        ngx_http_spdy_read_handler(rev);
+        return;
+    }
+
+    c = rev->data;
+
+    ngx_http_spdy_handle_connection(c->data);
+}
+
+
+void
+ngx_http_spdy_close_stream(ngx_http_spdy_stream_t *stream, ngx_int_t rc)
+{
+    ngx_event_t                 *ev;
+    ngx_connection_t            *fc;
+    ngx_http_spdy_stream_t     **index, *s;
+    ngx_http_spdy_srv_conf_t    *sscf;
+    ngx_http_spdy_connection_t  *sc;
+
+    sc = stream->connection;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+                   "spdy close stream %ui, processing %ui",
+                   stream->id, sc->processing);
+
+    sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx,
+                                        ngx_http_spdy_module);
+
+    index = sc->streams_index + ngx_http_spdy_stream_index(sscf, stream->id);
+
+    for ( ;; ) {
+        s = *index;
+
+        if (s == NULL) {
+            break;
+        }
+
+        if (s == stream) {
+            *index = s->index;
+            break;
+        }
+
+        index = &s->index;
+    }
+
+    fc = stream->request->connection;
+
+    ngx_http_free_request(stream->request, rc);
+
+    ev = fc->read;
+
+    if (ev->active || ev->disabled) {
+        ngx_del_event(ev, NGX_READ_EVENT, 0);
+    }
+
+    if (ev->timer_set) {
+        ngx_del_timer(ev);
+    }
+
+    if (ev->prev) {
+        ngx_delete_posted_event(ev);
+    }
+
+    ev = fc->write;
+
+    if (ev->active || ev->disabled) {
+        ngx_del_event(ev, NGX_WRITE_EVENT, 0);
+    }
+
+    if (ev->timer_set) {
+        ngx_del_timer(ev);
+    }
+
+    if (ev->prev) {
+        ngx_delete_posted_event(ev);
+    }
+
+    fc->data = sc->free_fake_connections;
+    sc->free_fake_connections = fc;
+
+    sc->processing--;
+
+    if (sc->processing || sc->blocked) {
+        return;
+    }
+
+    ev = sc->connection->read;
+
+    ev->handler = ngx_http_spdy_handle_connection_handler;
+    ngx_post_event(ev, &ngx_posted_events);
+}
+
+
+static void
+ngx_http_spdy_handle_connection(ngx_http_spdy_connection_t *sc)
+{
+    ngx_connection_t          *c;
+    ngx_http_spdy_srv_conf_t  *sscf;
+
+    if (sc->last_out || sc->processing) {
+        return;
+    }
+
+    c = sc->connection;
+
+    if (c->error) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    if (c->buffered) {
+        return;
+    }
+
+    sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx,
+                                        ngx_http_spdy_module);
+    if (sc->waiting) {
+        ngx_add_timer(c->read, sscf->recv_timeout);
+        return;
+    }
+
+    if (ngx_terminate || ngx_exiting) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    ngx_destroy_pool(sc->pool);
+
+    sc->pool = NULL;
+    sc->free_ctrl_frames = NULL;
+    sc->free_fake_connections = NULL;
+
+#if (NGX_HTTP_SSL)
+    if (c->ssl) {
+        ngx_ssl_free_buffer(c);
+    }
+#endif
+
+    c->destroyed = 1;
+    c->idle = 1;
+    ngx_reusable_connection(c, 1);
+
+    c->write->handler = ngx_http_empty_handler;
+    c->read->handler = ngx_http_spdy_keepalive_handler;
+
+    ngx_add_timer(c->read, sscf->keepalive_timeout);
+}
+
+
+static void
+ngx_http_spdy_keepalive_handler(ngx_event_t *rev)
+{
+    ngx_connection_t            *c;
+    ngx_http_spdy_srv_conf_t    *sscf;
+    ngx_http_spdy_connection_t  *sc;
+
+    c = rev->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "spdy keepalive handler");
+
+    if (rev->timedout || c->close) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+#if (NGX_HAVE_KQUEUE)
+
+    if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+        if (rev->pending_eof) {
+            c->log->handler = NULL;
+            ngx_log_error(NGX_LOG_INFO, c->log, rev->kq_errno,
+                          "kevent() reported that client %V closed "
+                          "keepalive connection", &c->addr_text);
+#if (NGX_HTTP_SSL)
+            if (c->ssl) {
+                c->ssl->no_send_shutdown = 1;
+            }
+#endif
+            ngx_http_close_connection(c);
+            return;
+        }
+    }
+
+#endif
+
+    c->destroyed = 0;
+    c->idle = 0;
+    ngx_reusable_connection(c, 0);
+
+    sc = c->data;
+
+    sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx,
+                                        ngx_http_spdy_module);
+
+    sc->pool = ngx_create_pool(sscf->pool_size, sc->connection->log);
+    if (sc->pool == NULL) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    sc->streams_index = ngx_pcalloc(sc->pool,
+                                    ngx_http_spdy_streams_index_size(sscf)
+                                    * sizeof(ngx_http_spdy_stream_t *));
+    if (sc->streams_index == NULL) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    c->write->handler = ngx_http_spdy_write_handler;
+
+    rev->handler = ngx_http_spdy_read_handler;
+    ngx_http_spdy_read_handler(rev);
+}
+
+
+static void
+ngx_http_spdy_finalize_connection(ngx_http_spdy_connection_t *sc,
+    ngx_int_t rc)
+{
+    ngx_uint_t                 i, size;
+    ngx_event_t               *ev;
+    ngx_connection_t          *c, *fc;
+    ngx_http_request_t        *r;
+    ngx_http_spdy_stream_t    *stream;
+    ngx_http_spdy_srv_conf_t  *sscf;
+
+    c = sc->connection;
+
+    if (!sc->processing) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    c->error = 1;
+
+    sc->last_out = NULL;
+
+    sc->blocked = 1;
+
+    sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx,
+                                        ngx_http_spdy_module);
+
+    size = ngx_http_spdy_streams_index_size(sscf);
+
+    for (i = 0; i < size; i++) {
+        stream = sc->streams_index[i];
+
+        while (stream) {
+            r = stream->request;
+
+            fc = r->connection;
+            fc->error = 1;
+
+            if (stream->waiting) {
+                r->blocked -= stream->waiting;
+                stream->waiting = 0;
+                ev = fc->write;
+
+            } else {
+                ev = fc->read;
+            }
+
+            stream = stream->index;
+
+            ev->eof = 1;
+            ev->handler(ev);
+        }
+    }
+
+    sc->blocked = 0;
+
+    if (sc->processing) {
+        return;
+    }
+
+    ngx_http_close_connection(c);
+}
diff -r 7ecddb37e4c8 -r 0fed3becc056 src/http/ngx_http_spdy.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/http/ngx_http_spdy.h	Wed Mar 13 02:58:14 2013 +0400
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Valentin V. Bartenev
+ */
+
+
+#ifndef _NGX_HTTP_SPDY_H_INCLUDED_
+#define _NGX_HTTP_SPDY_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#include <zlib.h>
+
+
+#define NGX_SPDY_VERSION              2
+
+#ifdef TLSEXT_TYPE_next_proto_neg
+#define NGX_SPDY_NPN_ADVERTISE        "\x06spdy/2"
+#endif
+
+#define NGX_SPDY_STATE_BUFFER_SIZE    9
+
+#define NGX_SPDY_HIGHEST_PRIORITY     0
+#define NGX_SPDY_LOWEST_PRIORITY      3
+
+#define NGX_SPDY_FLAG_FIN             0x01
+#define NGX_SPDY_FLAG_UNIDIRECTIONAL  0x02
+
+#define NGX_SPDY_MAX_FRAME_SIZE       ((1 << 24) - 1)
+
+
+typedef struct ngx_http_spdy_connection_s   ngx_http_spdy_connection_t;
+typedef struct ngx_http_spdy_stream_s       ngx_http_spdy_stream_t;
+typedef struct ngx_http_spdy_out_frame_s    ngx_http_spdy_out_frame_t;
+
+
+typedef ngx_int_t (*ngx_http_spdy_handler_pt) (ngx_http_spdy_connection_t *sc,
+    u_char **pos, size_t size);
+
+struct ngx_http_spdy_connection_s {
+    ngx_connection_t                *connection;
+    ngx_http_connection_t           *http_connection;
+
+    ngx_uint_t                       processing;
+
+    u_char                           buffer[NGX_SPDY_STATE_BUFFER_SIZE];
+    size_t                           buffer_used;
+    ngx_http_spdy_handler_pt         handler;
+
+    z_stream                         zstream_in;
+    z_stream                         zstream_out;
+
+    ngx_pool_t                      *pool;
+
+    ngx_http_spdy_out_frame_t       *free_ctrl_frames;
+    ngx_connection_t                *free_fake_connections;
+
+    ngx_http_spdy_stream_t         **streams_index;
+
+    ngx_http_spdy_out_frame_t       *last_out;
+    ngx_http_spdy_stream_t          *last_stream;
+
+    ngx_http_spdy_stream_t          *stream;
+
+    ngx_uint_t                       headers;
+    size_t                           length;
+    u_char                           flags;
+
+    ngx_uint_t                       last_sid;
+
+    unsigned                         blocked:2;
+    unsigned                         waiting:1; /* FIXME better name */
+};
+
+
+struct ngx_http_spdy_stream_s {
+    ngx_uint_t                       id;
+    ngx_http_request_t              *request;
+    ngx_http_spdy_connection_t      *connection;
+    ngx_http_spdy_stream_t          *index;
+    ngx_http_spdy_stream_t          *next;
+
+    ngx_uint_t                       header_buffers;
+    ngx_uint_t                       waiting;
+    ngx_http_spdy_out_frame_t       *free_frames;
+    ngx_chain_t                     *free_data_headers;
+
+    unsigned                         priority:2;
+    unsigned                         handled:1;
+    unsigned                         half_closed:1;
+    unsigned                         skip_data:1;
+};
+
+
+struct ngx_http_spdy_out_frame_s {
+    ngx_http_spdy_out_frame_t       *next;
+    ngx_chain_t                     *first;
+    ngx_chain_t                     *last;
+    ngx_int_t                      (*handler)(ngx_http_spdy_connection_t *sc,
+                                        ngx_http_spdy_out_frame_t *frame);
+
+    ngx_http_spdy_out_frame_t       *free;
+
+    ngx_http_spdy_stream_t          *stream;
+    size_t                           size;
+
+    ngx_uint_t                       blocked;
+    ngx_uint_t                       priority;
+};
+
+
+static ngx_inline void
+ngx_http_spdy_queue_frame(ngx_http_spdy_connection_t *sc,
+    ngx_http_spdy_out_frame_t *frame)
+{
+    ngx_http_spdy_out_frame_t  **out;
+
+    for (out = &sc->last_out; *out; out = &(*out)->next) {
+
+        if ((frame->blocked && (*out)->blocked)
+            || frame->priority >= (*out)->priority)
+        {
+            break;
+        }
+    }
+
+    frame->next = *out;
+    *out = frame;
+}
+
+
+void ngx_http_spdy_init(ngx_event_t *rev);
+
+ngx_int_t ngx_http_spdy_init_request_body(ngx_http_request_t *r);
+
+void ngx_http_spdy_close_stream(ngx_http_spdy_stream_t *stream, ngx_int_t rc);
+
+ngx_int_t ngx_http_spdy_send_output_queue(ngx_http_spdy_connection_t *sc);
+
+void ngx_http_spdy_request_headers_init();
+
+
+#define ngx_spdy_frame_aligned_write_uint16(p, s)                             \
+    (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t))
+
+#define ngx_spdy_frame_aligned_write_uint32(p, s)                             \
+    (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t))
+
+#define ngx_spdy_frame_aligned_write_flags_and_len(p, f, s)                   \
+    (*(uint32_t *) (p) = htonl((uint32_t) (f) << 24 | (s)),                   \
+     (p) + sizeof(uint32_t))
+
+#if (NGX_HAVE_NONALIGNED)
+
+#if (NGX_HAVE_LITTLE_ENDIAN)
+#define ngx_http_spdy_detect(p)  ((*(uint32_t *) p << 8) == 0x00028000)
+#else
+#define ngx_http_spdy_detect(p)  ((*(uint32_t *) p >> 8) == 0x00800200)
+#endif
+
+#define ngx_spdy_frame_write_uint16  ngx_spdy_frame_aligned_write_uint16
+#define ngx_spdy_frame_write_uint32  ngx_spdy_frame_aligned_write_uint32
+#define ngx_spdy_frame_write_flags_and_len                                    \
+    ngx_spdy_frame_aligned_write_flags_and_len
+
+#else
+
+#define ngx_http_spdy_detect(p)  (p[0] == 0x80 && p[1] == 0x02 && p[2] == 0x00)
+
+#define ngx_spdy_frame_write_uint16(p, s)                                     \
+    ((p)[0] = (u_char) (s) >> 8, (p)[1] = (u_char) (s), (p) + sizeof(uint16_t))
+
+#define ngx_spdy_frame_write_uint32(p, s)                                     \
+    ((p)[0] = (u_char) (s) >> 24,                                             \
+    (p)[1] = (u_char) (s) >> 16,                                              \
+    (p)[2] = (u_char) (s) >> 8,                                               \
+    (p)[3] = (u_char) (s), (p) + sizeof(uint32_t))
+
+#define ngx_spdy_frame_write_flags_and_len(p, f, s)                           \
+    ((p)[0] = (u_char) (f),                                                   \
+    (p)[1] = (u_char) ((s) >> 16),                                            \
+    (p)[2] = (u_char) ((s) >> 8),                                             \
+    (p)[3] = (u_char) (s), (p) + sizeof(uint32_t))
+
+#endif
+
+#endif /* _NGX_HTTP_SPDY_H_INCLUDED_ */
diff -r 7ecddb37e4c8 -r 0fed3becc056 src/http/ngx_http_spdy_filter_module.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/http/ngx_http_spdy_filter_module.c	Wed Mar 13 02:58:14 2013 +0400
@@ -0,0 +1,976 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Valentin V. Bartenev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <nginx.h>
+
+#include <zlib.h>
+
+
+#define NGX_SPDY_WRITE_BUFFERED  NGX_HTTP_WRITE_BUFFERED
+
+#define ngx_http_spdy_header_sizeof(h)  (2 + sizeof(h) - 1)
+
+#define ngx_http_spdy_header_write(p, h)                                      \
+    ngx_cpymem(ngx_spdy_frame_write_uint16(p, sizeof(h) - 1), h, sizeof(h) - 1)
+
+static ngx_inline ngx_int_t ngx_http_spdy_filter_send(
+    ngx_connection_t *fc, ngx_http_spdy_stream_t *stream);
+
+static ngx_inline void ngx_http_spdy_handle_stream(
+    ngx_http_spdy_connection_t *sc, ngx_http_spdy_stream_t *stream);
+
+static ngx_http_spdy_out_frame_t *ngx_http_spdy_filter_get_data_frame(
+    ngx_http_spdy_stream_t *stream, size_t len, ngx_uint_t flags,
+    ngx_chain_t *first, ngx_chain_t *last);
+
+static ngx_int_t ngx_http_spdy_syn_frame_handler(
+    ngx_http_spdy_connection_t *sc, ngx_http_spdy_out_frame_t *frame);
+static ngx_int_t ngx_http_spdy_data_frame_handler(
+    ngx_http_spdy_connection_t *sc, ngx_http_spdy_out_frame_t *frame);
+
+static void ngx_http_spdy_filter_cleanup(void *data);
+
+static ngx_int_t ngx_http_spdy_filter_init(ngx_conf_t *cf);
+
+
+static ngx_http_module_t  ngx_http_spdy_filter_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    ngx_http_spdy_filter_init,             /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL                                   /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_spdy_filter_module = {
+    NGX_MODULE_V1,
+    &ngx_http_spdy_filter_module_ctx,      /* module context */
+    NULL,                                  /* 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_http_output_header_filter_pt  ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
+
+
+static ngx_int_t
+ngx_http_spdy_header_filter(ngx_http_request_t *r)
+{
+    int                           rc;
+    size_t                        len;
+    u_char                       *p, *buf, *last;
+    ngx_buf_t                    *b;
+    ngx_str_t                     host;
+    ngx_uint_t                    i, j, count, port;
+    ngx_chain_t                  *cl;
+    ngx_list_part_t              *part, *pt;
+    ngx_table_elt_t              *header, *h;
+    ngx_connection_t             *c;
+    ngx_http_cleanup_t           *cln;
+    ngx_http_core_loc_conf_t     *clcf;
+    ngx_http_core_srv_conf_t     *cscf;
+    ngx_http_spdy_stream_t       *stream;
+    ngx_http_spdy_out_frame_t    *frame;
+    ngx_http_spdy_connection_t   *sc;
+    struct sockaddr_in           *sin;
+#if (NGX_HAVE_INET6)
+    struct sockaddr_in6          *sin6;
+#endif
+    u_char                        addr[NGX_SOCKADDR_STRLEN];
+
+    if (!r->spdy_stream) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "spdy header filter");
+
+    if (r->header_sent) {
+        return NGX_OK;
+    }
+
+    r->header_sent = 1;
+
+    if (r != r->main) {
+        return NGX_OK;
+    }
+
+    c = r->connection;
+
+    if (r->method == NGX_HTTP_HEAD) {
+        r->header_only = 1;
+    }
+
+    switch (r->headers_out.status) {
+
+    case NGX_HTTP_OK:
+    case NGX_HTTP_PARTIAL_CONTENT:
+        break;
+
+    case NGX_HTTP_NOT_MODIFIED:
+        r->header_only = 1;
+        break;
+
+    case NGX_HTTP_NO_CONTENT:
+        r->header_only = 1;
+
+        ngx_str_null(&r->headers_out.content_type);
+
+        r->headers_out.content_length = NULL;
+        r->headers_out.content_length_n = -1;
+
+        /* fall through */
+
+    default:
+        r->headers_out.last_modified_time = -1;
+        r->headers_out.last_modified = NULL;
+    }
+
+    len = 8 + 6 + 2
+          + ngx_http_spdy_header_sizeof("version")
+          + ngx_http_spdy_header_sizeof("HTTP/1.x")
+          + ngx_http_spdy_header_sizeof("status")
+          + ngx_http_spdy_header_sizeof("xxx");
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (r->headers_out.server == NULL) {
+        len += ngx_http_spdy_header_sizeof("server");
+        len += clcf->server_tokens ? ngx_http_spdy_header_sizeof(NGINX_VER):
+                                     ngx_http_spdy_header_sizeof("nginx");
+    }
+
+    if (r->headers_out.date == NULL) {
+        len += ngx_http_spdy_header_sizeof("date")
+               + ngx_http_spdy_header_sizeof("Wed, 31 Dec 1986 10:00:00 GMT");
+    }
+
+    if (r->headers_out.content_type.len) {
+        len += ngx_http_spdy_header_sizeof("content-type")
+               + 2 + r->headers_out.content_type.len;
+
+        if (r->headers_out.content_type_len == r->headers_out.content_type.len
+            && r->headers_out.charset.len)
+        {
+            len += sizeof("; charset=") - 1 + r->headers_out.charset.len;
+        }
+    }
+
+    if (r->headers_out.content_length == NULL
+        && r->headers_out.content_length_n >= 0)
+    {
+        len += ngx_http_spdy_header_sizeof("content-length")
+               + 2 + NGX_OFF_T_LEN;
+    }
+
+    if (r->headers_out.last_modified == NULL
+        && r->headers_out.last_modified_time != -1)
+    {
+        len += ngx_http_spdy_header_sizeof("last-modified")
+               + ngx_http_spdy_header_sizeof("Wed, 31 Dec 1986 10:00:00 GMT");
+    }
+
+    if (r->headers_out.location
+        && r->headers_out.location->value.len
+        && r->headers_out.location->value.data[0] == '/')
+    {
+        r->headers_out.location->hash = 0;
+
+        if (clcf->server_name_in_redirect) {
+            cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+            host = cscf->server_name;
+
+        } else if (r->headers_in.server.len) {
+            host = r->headers_in.server;
+
+        } else {
+            host.len = NGX_SOCKADDR_STRLEN;
+            host.data = addr;
+
+            if (ngx_connection_local_sockaddr(c, &host, 0) != NGX_OK) {
+                return NGX_ERROR;
+            }
+        }
+
+        switch (c->local_sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+        case AF_INET6:
+            sin6 = (struct sockaddr_in6 *) c->local_sockaddr;
+            port = ntohs(sin6->sin6_port);
+            break;
+#endif
+#if (NGX_HAVE_UNIX_DOMAIN)
+        case AF_UNIX:
+            port = 0;
+            break;
+#endif
+        default: /* AF_INET */
+            sin = (struct sockaddr_in *) c->local_sockaddr;
+            port = ntohs(sin->sin_port);
+            break;
+        }
+
+        len += ngx_http_spdy_header_sizeof("location")
+               + ngx_http_spdy_header_sizeof("https://")
+               + host.len
+               + r->headers_out.location->value.len;
+
+        if (clcf->port_in_redirect) {
+
+#if (NGX_HTTP_SSL)
+            if (c->ssl)
+                port = (port == 443) ? 0 : port;
+            else
+#endif
+                port = (port == 80) ? 0 : port;
+
+        } else {
+            port = 0;
+        }
+
+        if (port) {
+            len += sizeof(":65535") - 1;
+        }
+
+    } else {
+        ngx_str_null(&host);
+        port = 0;
+    }
+
+    part = &r->headers_out.headers.part;
+    header = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            header = part->elts;
+            i = 0;
+        }
+
+        if (header[i].hash == 0) {
+            continue;
+        }
+
+        len += sizeof(uint16_t) + header[i].key.len
+               + sizeof(uint16_t) + header[i].value.len;
+    }
+
+    buf = ngx_alloc(len, r->pool->log);
+    if (buf == NULL) {
+        return NGX_ERROR;
+    }
+
+    last = buf + sizeof(uint16_t);
+
+    last = ngx_http_spdy_header_write(last, "version");
+    last = ngx_http_spdy_header_write(last, "HTTP/1.1");
+
+    last = ngx_http_spdy_header_write(last, "status");
+    last = ngx_spdy_frame_write_uint16(last, 3);
+    last = ngx_sprintf(last, "%03ui", r->headers_out.status);
+
+    count = 2;
+
+    if (r->headers_out.server == NULL) {
+        last = ngx_http_spdy_header_write(last, "server");
+        last = clcf->server_tokens ?
+                    ngx_http_spdy_header_write(last, NGINX_VER):
+                    ngx_http_spdy_header_write(last, "nginx");
+        count++;
+    }
+
+    if (r->headers_out.date == NULL) {
+        last = ngx_http_spdy_header_write(last, "date");
+
+        last = ngx_spdy_frame_write_uint16(last, ngx_cached_http_time.len);
+
+        last = ngx_cpymem(last, ngx_cached_http_time.data,
+                          ngx_cached_http_time.len);
+        count++;
+    }
+
+    if (r->headers_out.content_type.len) {
+
+        last = ngx_http_spdy_header_write(last, "content-type");
+
+        p = last + sizeof(uint16_t);
+
+        last = ngx_cpymem(p, r->headers_out.content_type.data,
+                          r->headers_out.content_type.len);
+
+        if (r->headers_out.content_type_len == r->headers_out.content_type.len
+            && r->headers_out.charset.len)
+        {
+            last = ngx_cpymem(last, "; charset=", sizeof("; charset=") - 1);
+
+            last = ngx_cpymem(last, r->headers_out.charset.data,
+                              r->headers_out.charset.len);
+
+            /* update r->headers_out.content_type for possible logging */
+
+            r->headers_out.content_type.len = last - p;
+            r->headers_out.content_type.data = p;
+        }
+
+        (void) ngx_spdy_frame_write_uint16(p - sizeof(uint16_t),
+                                           r->headers_out.content_type.len);
+
+        count++;
+    }
+
+    if (r->headers_out.content_length == NULL
+        && r->headers_out.content_length_n >= 0)
+    {
+        last = ngx_http_spdy_header_write(last, "content-length");
+
+        p = last + sizeof(uint16_t);
+
+        last = ngx_sprintf(p, "%O", r->headers_out.content_length_n);
+
+        (void) ngx_spdy_frame_write_uint16(p - sizeof(uint16_t), last - p);
+
+        count++;
+    }
+
+    if (r->headers_out.last_modified == NULL
+        && r->headers_out.last_modified_time != -1)
+    {
+        last = ngx_http_spdy_header_write(last, "last-modified");
+
+        p = last + sizeof(uint16_t);
+
+        last = ngx_http_time(p, r->headers_out.last_modified_time);
+
+        (void) ngx_spdy_frame_write_uint16(p - sizeof(uint16_t), last - p);
+
+        count++;
+    }
+
+    if (host.data) {
+
+        last = ngx_http_spdy_header_write(last, "location");
+
+        p = last + sizeof(uint16_t);
+
+        last = ngx_cpymem(p, "http", sizeof("http") - 1);
+
+#if (NGX_HTTP_SSL)
+        if (c->ssl) {
+            *last++ ='s';
+        }
+#endif
+
+        *last++ = ':'; *last++ = '/'; *last++ = '/';
+
+        last = ngx_cpymem(last, host.data, host.len);
+
+        if (port) {
+            last = ngx_sprintf(last, ":%ui", port);
+        }
+
+        last = ngx_cpymem(last, r->headers_out.location->value.data,
+                          r->headers_out.location->value.len);
+
+        /* update r->headers_out.location->value for possible logging */
+
+        r->headers_out.location->value.len = last - p;
+        r->headers_out.location->value.data = p;
+        ngx_str_set(&r->headers_out.location->key, "location");
+
+        (void) ngx_spdy_frame_write_uint16(p - sizeof(uint16_t),
+                                           r->headers_out.location->value.len);
+
+        count++;
+    }
+
+    part = &r->headers_out.headers.part;
+    header = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            header = part->elts;
+            i = 0;
+        }
+
+        if (header[i].hash == 0 || header[i].hash == 2) {
+            continue;
+        }
+
+        if ((header[i].key.len == 6
+             && ngx_strncasecmp(header[i].key.data,
+                                (u_char *) "status", 6) == 0)
+            || (header[i].key.len == 7
+                && ngx_strncasecmp(header[i].key.data,
+                                   (u_char *) "version", 7) == 0))
+        {
+            header[i].hash = 0;
+            continue;
+        }
+
+        last = ngx_spdy_frame_write_uint16(last, header[i].key.len);
+
+        ngx_strlow(last, header[i].key.data, header[i].key.len);
+        last += header[i].key.len;
+
+        p = last + sizeof(uint16_t);
+
+        last = ngx_cpymem(p, header[i].value.data, header[i].value.len);
+
+        pt = part;
+        h = header;
+
+        for (j = i + 1; /* void */; j++) {
+
+            if (j >= pt->nelts) {
+                if (pt->next == NULL) {
+                    break;
+                }
+
+                pt = pt->next;
+                h = pt->elts;
+                j = 0;
+            }
+
+            if (h[j].hash == 0 || h[j].hash == 2
+                || h[j].key.len != header[i].key.len
+                || ngx_strncasecmp(header[i].key.data, h[j].key.data,
+                                   header[i].key.len))
+            {
+                continue;
+            }
+
+            *last++ = '\0';
+
+            last = ngx_cpymem(last, h[j].value.data, h[j].value.len);
+
+            h[j].hash = 2;
+        }
+
+        (void) ngx_spdy_frame_write_uint16(p - sizeof(uint16_t), last - p);
+
+        count++;
+    }
+
+    (void) ngx_spdy_frame_write_uint16(buf, count);
+
+    stream = r->spdy_stream;
+    sc = stream->connection;
+
+    len = last - buf;
+
+    b = ngx_create_temp_buf(r->pool, 14 + deflateBound(&sc->zstream_out, len));
+    if (b == NULL) {
+        ngx_free(buf);
+        return NGX_ERROR;
+    }
+
+    b->last += 14;
+
+    sc->zstream_out.next_in = buf;
+    sc->zstream_out.avail_in = len;
+    sc->zstream_out.next_out = b->last;
+    sc->zstream_out.avail_out = b->end - b->last;
+
+    rc = deflate(&sc->zstream_out, Z_SYNC_FLUSH);
+
+    ngx_free(buf);
+
+    if (rc != Z_OK) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                      "spdy deflate() failed: %d", rc);
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "spdy deflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d",
+                   sc->zstream_out.next_in, sc->zstream_out.next_out,
+                   sc->zstream_out.avail_in, sc->zstream_out.avail_out,
+                   rc);
+
+    b->last = sc->zstream_out.next_out;
+
+    p = b->pos;
+
+    p = ngx_spdy_frame_aligned_write_uint32(p, 0x80020002);
+
+    len = b->last - b->pos;
+
+    r->header_size = len;
+
+    if (r->header_only) {
+        b->last_buf = 1;
+        p = ngx_spdy_frame_aligned_write_flags_and_len(p, NGX_SPDY_FLAG_FIN,
+                                                       len - 8);
+    } else {
+        p = ngx_spdy_frame_aligned_write_flags_and_len(p, 0, len - 8);
+    }
+
+    (void) ngx_spdy_frame_aligned_write_uint32(p, stream->id);
+
+    cl = ngx_alloc_chain_link(r->pool);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    cl->buf = b;
+    cl->next = NULL;
+
+    frame = ngx_palloc(r->pool, sizeof(ngx_http_spdy_out_frame_t));
+    if (frame == NULL) {
+        return NGX_ERROR;
+    }
+
+    frame->first = cl;
+    frame->last = cl;
+    frame->handler = ngx_http_spdy_syn_frame_handler;
+    frame->free = NULL;
+    frame->stream = stream;
+    frame->size = len;
+    frame->blocked = 1;
+    frame->priority = stream->priority;
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, stream->request->connection->log, 0,
+                   "spdy:%ui create SYN_REPLY frame %p: size:%ui",
+                   stream->id, frame, frame->size);
+
+    ngx_http_spdy_queue_frame(sc, frame);
+
+    r->blocked++;
+
+    cln = ngx_http_cleanup_add(r, 0);
+    if (cln == NULL) {
+        return NGX_ERROR;
+    }
+
+    cln->handler = ngx_http_spdy_filter_cleanup;
+    cln->data = stream;
+
+    stream->waiting = 1;
+
+    return ngx_http_spdy_filter_send(c, stream);
+}
+
+
+static ngx_int_t
+ngx_http_spdy_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    off_t                       size;
+    ngx_buf_t                  *b;
+    ngx_uint_t                  flags;
+    ngx_chain_t                *cl, *ll, *out, **ln;
+    ngx_http_spdy_stream_t     *stream;
+    ngx_http_spdy_out_frame_t  *frame;
+
+    stream = r->spdy_stream;
+
+    if (stream == NULL) {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "spdy body filter \"%V?%V\"", &r->uri, &r->args);
+
+    if (in == NULL || r->header_only) {
+
+        if (stream->waiting) {
+            return NGX_AGAIN;
+        }
+
+        r->connection->buffered &= ~NGX_SPDY_WRITE_BUFFERED;
+
+        return NGX_OK;
+    }
+
+    size = 0;
+    ln = &out;
+    ll = in;
+
+    for ( ;; ) {
+        b = ll->buf;
+#if 1
+        if (ngx_buf_size(b) == 0 && !ngx_buf_special(b)) {
+            ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                          "zero size buf in spdy body filter "
+                          "t:%d r:%d f:%d %p %p-%p %p %O-%O",
+                          b->temporary,
+                          b->recycled,
+                          b->in_file,
+                          b->start,
+                          b->pos,
+                          b->last,
+                          b->file,
+                          b->file_pos,
+                          b->file_last);
+
+            ngx_debug_point();
+            return NGX_ERROR;
+        }
+#endif
+        cl = ngx_alloc_chain_link(r->pool);
+        if (cl == NULL) {
+            return NGX_ERROR;
+        }
+
+        size += ngx_buf_size(b);
+        cl->buf = b;
+
+        *ln = cl;
+        ln = &cl->next;
+
+        if (ll->next == NULL) {
+            break;
+        }
+
+        ll = ll->next;
+    }
+
+    if (size > NGX_SPDY_MAX_FRAME_SIZE) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                      "FIXME: too big chain in spdy filter: %O", size);
+        return NGX_ERROR;
+    }
+
+    flags = b->last_buf ? NGX_SPDY_FLAG_FIN : 0;
+
+    frame = ngx_http_spdy_filter_get_data_frame(stream, (size_t) size, flags,
+                                                out, cl);
+    if (frame == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_http_spdy_queue_frame(stream->connection, frame);
+
+    stream->waiting++;
+
+    r->main->blocked++;
+
+    return ngx_http_spdy_filter_send(r->connection, stream);
+}
+
+
+static ngx_http_spdy_out_frame_t *
+ngx_http_spdy_filter_get_data_frame(ngx_http_spdy_stream_t *stream,
+    size_t len, ngx_uint_t flags, ngx_chain_t *first, ngx_chain_t *last)
+{
+    u_char                     *p;
+    ngx_buf_t                  *buf;
+    ngx_chain_t                *cl;
+    ngx_http_spdy_out_frame_t  *frame;
+
+
+    frame = stream->free_frames;
+
+    if (frame) {
+        stream->free_frames = frame->free;
+
+    } else {
+        frame = ngx_palloc(stream->request->pool,
+                           sizeof(ngx_http_spdy_out_frame_t));
+        if (frame == NULL) {
+            return NULL;
+        }
+    }
+
+    ngx_log_debug4(NGX_LOG_DEBUG_HTTP, stream->request->connection->log, 0,
+                   "spdy:%ui create DATA frame %p: len:%i flags:%ui",
+                   stream->id, frame, len, flags);
+
+    if (len || flags) {
+        cl = ngx_chain_get_free_buf(stream->request->pool,
+                                    &stream->free_data_headers);
+        if (cl == NULL) {
+            return NULL;
+        }
+
+        buf = cl->buf;
+
+        if (buf->start) {
+            p = buf->start;
+            buf->pos = p;
+
+            p += sizeof(uint32_t);
+
+            (void) ngx_spdy_frame_aligned_write_flags_and_len(p, flags, len);
+
+        } else {
+            p = ngx_palloc(stream->request->pool, 8);
+            if (p == NULL) {
+                return NULL;
+            }
+
+            buf->pos = p;
+            buf->start = p;
+
+            p = ngx_spdy_frame_aligned_write_uint32(p, stream->id);
+
+            p = ngx_spdy_frame_aligned_write_flags_and_len(p, flags, len);
+
+            buf->last = p;
+            buf->end = p;
+
+            buf->tag = (ngx_buf_tag_t) &ngx_http_spdy_filter_module;
+            buf->memory = 1;
+        }
+
+        cl->next = first;
+        first = cl;
+    }
+
+    frame->first = first;
+    frame->last = last;
+    frame->handler = ngx_http_spdy_data_frame_handler;
+    frame->free = NULL;
+    frame->stream = stream;
+    frame->size = 8 + len;
+    frame->blocked = 0;
+    frame->priority = stream->priority;
+
+    return frame;
+}
+
+
+static ngx_inline ngx_int_t
+ngx_http_spdy_filter_send(ngx_connection_t *fc, ngx_http_spdy_stream_t *stream)
+{
+    if (ngx_http_spdy_send_output_queue(stream->connection) == NGX_ERROR) {
+        fc->error = 1;
+        return NGX_ERROR;
+    }
+
+    if (stream->waiting) {
+        fc->buffered |= NGX_SPDY_WRITE_BUFFERED;
+        fc->write->delayed = 1;
+        return NGX_AGAIN;
+    }
+
+    fc->buffered &= ~NGX_SPDY_WRITE_BUFFERED;
+
+    return NGX_OK;
+}
+
+
+static ngx_inline void
+ngx_http_spdy_handle_stream(ngx_http_spdy_connection_t *sc,
+    ngx_http_spdy_stream_t *stream)
+{
+    ngx_connection_t  *fc;
+
+    fc = stream->request->connection;
+
+    fc->write->delayed = 0;
+
+    if (stream->handled) {
+        return;
+    }
+
+    if (sc->blocked == 2) {
+        stream->handled = 1;
+
+        stream->next = sc->last_stream;
+        sc->last_stream = stream;
+    }
+}
+
+
+static ngx_int_t
+ngx_http_spdy_syn_frame_handler(ngx_http_spdy_connection_t *sc,
+    ngx_http_spdy_out_frame_t *frame)
+{
+    ngx_buf_t               *buf;
+    ngx_http_request_t      *r;
+    ngx_http_spdy_stream_t  *stream;
+
+    buf = frame->first->buf;
+
+    if (buf->pos != buf->last) {
+        return NGX_AGAIN;
+    }
+
+    stream = frame->stream;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+                   "spdy:%ui SYN_REPLY frame %p was sent", stream->id, frame);
+
+    r = stream->request;
+
+    r->blocked--;
+
+    r->connection->sent += frame->size;
+
+    ngx_free_chain(r->pool, frame->first);
+
+    frame->free = stream->free_frames;
+    stream->free_frames = frame;
+
+    stream->waiting--;
+
+    ngx_http_spdy_handle_stream(sc, stream);
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_spdy_data_frame_handler(ngx_http_spdy_connection_t *sc,
+    ngx_http_spdy_out_frame_t *frame)
+{
+    ngx_chain_t             *cl, *ln;
+    ngx_http_request_t      *r;
+    ngx_http_spdy_stream_t  *stream;
+
+    stream = frame->stream;
+    r = stream->request;
+
+    cl = frame->first;
+
+    if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_spdy_filter_module) {
+
+        if (cl->buf->pos != cl->buf->last) {
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+                           "spdy:%ui DATA frame %p was sent partially",
+                           stream->id, frame);
+
+            return NGX_AGAIN;
+        }
+
+        ln = cl->next;
+
+        cl->next = stream->free_data_headers;
+        stream->free_data_headers = cl;
+
+        if (cl == frame->last) {
+            goto done;
+        }
+
+        cl = ln;
+    }
+
+    for ( ;; ) {
+        if (ngx_buf_size(cl->buf) != 0) {
+
+            if (cl != frame->first) {
+                frame->first = cl;
+                ngx_http_spdy_handle_stream(sc, stream);
+            }
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+                           "spdy:%ui DATA frame %p was sent partially",
+                           stream->id, frame);
+
+            return NGX_AGAIN;
+        }
+
+        ln = cl->next;
+
+        ngx_free_chain(r->pool, cl);
+
+        if (cl == frame->last) {
+            goto done;
+        }
+
+        cl = ln;
+    }
+
+done:
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
+                   "spdy:%ui DATA frame %p was sent", stream->id, frame);
+
+    r->blocked--;
+
+    r->header_size += 8;
+    r->connection->sent += frame->size;
+
+    frame->free = stream->free_frames;
+    stream->free_frames = frame;
+
+    stream->waiting--;
+
+    ngx_http_spdy_handle_stream(sc, stream);
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_spdy_filter_cleanup(void *data)
+{
+    ngx_http_spdy_stream_t *stream = data;
+
+    ngx_http_request_t         *r;
+    ngx_http_spdy_out_frame_t  *frame, **fn;
+
+    if (stream->waiting == 0) {
+        return;
+    }
+
+    r = stream->request;
+
+    fn = &stream->connection->last_out;
+
+    for ( ;; ) {
+        frame = *fn;
+
+        if (frame == NULL) {
+            break;
+        }
+
+        if (frame->stream == stream && !frame->blocked) {
+
+            stream->waiting--;
+            r->blocked--;
+
+            *fn = frame->next;
+            continue;
+        }
+
+        fn = &frame->next;
+    }
+}
+
+
+static ngx_int_t
+ngx_http_spdy_filter_init(ngx_conf_t *cf)
+{
+    ngx_http_next_header_filter = ngx_http_top_header_filter;
+    ngx_http_top_header_filter = ngx_http_spdy_header_filter;
+
+    ngx_http_next_body_filter = ngx_http_top_body_filter;
+    ngx_http_top_body_filter = ngx_http_spdy_body_filter;
+
+    return NGX_OK;
+}
diff -r 7ecddb37e4c8 -r 0fed3becc056 src/http/ngx_http_spdy_module.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/http/ngx_http_spdy_module.c	Wed Mar 13 02:58:14 2013 +0400
@@ -0,0 +1,326 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Valentin V. Bartenev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_int_t ngx_http_spdy_add_variables(ngx_conf_t *cf);
+static ngx_int_t ngx_http_spdy_module_init(ngx_conf_t *cf);
+
+static ngx_int_t ngx_http_spdy_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_spdy_request_priority_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+
+static void *ngx_http_spdy_create_main_conf(ngx_conf_t *cf);
+static char *ngx_http_spdy_init_main_conf(ngx_conf_t *cf, void *conf);
+
+static void *ngx_http_spdy_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_http_spdy_merge_srv_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+
+static char *ngx_http_spdy_streams_index_mask(ngx_conf_t *cf, void *post,
+    void *data);
+
+
+static ngx_conf_num_bounds_t  ngx_http_spdy_headers_comp_bounds = {
+    ngx_conf_check_num_bounds, 0, 9
+};
+
+static ngx_conf_post_handler_pt  ngx_http_core_pool_size_p =
+    ngx_http_core_pool_size;
+static ngx_conf_post_handler_pt
+    ngx_http_spdy_streams_index_mask_p = ngx_http_spdy_streams_index_mask;
+
+
+static ngx_command_t  ngx_http_spdy_commands[] = {
+
+    { ngx_string("spdy_recv_buffer_size"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      offsetof(ngx_http_spdy_main_conf_t, recv_buffer_size),
+      NULL },
+
+    { ngx_string("spdy_pool_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_spdy_srv_conf_t, pool_size),
+      &ngx_http_core_pool_size_p },
+
+    { ngx_string("spdy_max_concurrent_streams"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_spdy_srv_conf_t, concurrent_streams),
+      NULL },
+
+    { ngx_string("spdy_streams_index_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_spdy_srv_conf_t, streams_index_mask),
+      &ngx_http_spdy_streams_index_mask_p },
+
+    { ngx_string("spdy_recv_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_spdy_srv_conf_t, recv_timeout),
+      NULL },
+
+    { ngx_string("spdy_keepalive_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_spdy_srv_conf_t, keepalive_timeout),
+      NULL },
+
+    { ngx_string("spdy_headers_comp"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_spdy_srv_conf_t, headers_comp),
+      &ngx_http_spdy_headers_comp_bounds },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_spdy_module_ctx = {
+    ngx_http_spdy_add_variables,           /* preconfiguration */
+    ngx_http_spdy_module_init,             /* postconfiguration */
+
+    ngx_http_spdy_create_main_conf,        /* create main configuration */
+    ngx_http_spdy_init_main_conf,          /* init main configuration */
+
+    ngx_http_spdy_create_srv_conf,         /* create server configuration */
+    ngx_http_spdy_merge_srv_conf,          /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL                                   /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_spdy_module = {
+    NGX_MODULE_V1,
+    &ngx_http_spdy_module_ctx,             /* module context */
+    ngx_http_spdy_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_http_variable_t  ngx_http_spdy_vars[] = {
+
+    { ngx_string("spdy"), NULL,
+      ngx_http_spdy_variable, 0, 0, 0 },
+
+    { ngx_string("spdy_request_priority"), NULL,
+      ngx_http_spdy_request_priority_variable, 0, 0, 0 },
+
+    { ngx_null_string, NULL, NULL, 0, 0, 0 }
+};
+
+
+static ngx_int_t
+ngx_http_spdy_add_variables(ngx_conf_t *cf)
+{
+   ngx_http_variable_t  *var, *v;
+
+    for (v = ngx_http_spdy_vars; v->name.len; v++) {
+        var = ngx_http_add_variable(cf, &v->name, v->flags);
+        if (var == NULL) {
+            return NGX_ERROR;
+        }
+
+        var->get_handler = v->get_handler;
+        var->data = v->data;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_spdy_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    if (r->spdy_stream) {
+        v->len = 1;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = (u_char *) "2";
+
+        return NGX_OK;
+    }
+
+    *v = ngx_http_variable_null_value;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_spdy_request_priority_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    if (r->spdy_stream) {
+        v->len = 1;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+
+        v->data = ngx_pnalloc(r->pool, 1);
+        if (v->data == NULL) {
+            return NGX_ERROR;
+        }
+
+        v->data[0] = (u_char) r->spdy_stream->priority | 0x30;
+
+        return NGX_OK;
+    }
+
+    *v = ngx_http_variable_null_value;
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_spdy_create_main_conf(ngx_conf_t *cf)
+{
+    ngx_http_spdy_main_conf_t  *smcf;
+
+    smcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_spdy_main_conf_t));
+    if (smcf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     smcf->enabled = 0;
+     */
+
+    smcf->recv_buffer_size = NGX_CONF_UNSET_SIZE;
+
+    return smcf;
+}
+
+
+static char *
+ngx_http_spdy_init_main_conf(ngx_conf_t *cf, void *conf)
+{
+    ngx_http_spdy_main_conf_t *smcf = conf;
+
+    if (smcf->recv_buffer_size == NGX_CONF_UNSET_SIZE) {
+        smcf->recv_buffer_size = 256 * 1024;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_http_spdy_create_srv_conf(ngx_conf_t *cf)
+{
+    ngx_http_spdy_srv_conf_t  *sscf;
+
+    sscf = ngx_pcalloc(cf->pool, sizeof(ngx_http_spdy_srv_conf_t));
+    if (sscf == NULL) {
+        return NULL;
+    }
+
+    sscf->pool_size = NGX_CONF_UNSET_SIZE;
+
+    sscf->concurrent_streams = NGX_CONF_UNSET_UINT;
+    sscf->streams_index_mask = NGX_CONF_UNSET_UINT;
+
+    sscf->recv_timeout = NGX_CONF_UNSET_MSEC;
+    sscf->keepalive_timeout = NGX_CONF_UNSET_MSEC;
+
+    sscf->headers_comp = NGX_CONF_UNSET;
+
+    return sscf;
+}
+
+
+static char *
+ngx_http_spdy_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_spdy_srv_conf_t *prev = parent;
+    ngx_http_spdy_srv_conf_t *conf = child;
+
+    ngx_conf_merge_size_value(conf->pool_size, prev->pool_size, 4096);
+
+    ngx_conf_merge_uint_value(conf->concurrent_streams,
+                              prev->concurrent_streams, 100);
+
+    ngx_conf_merge_uint_value(conf->streams_index_mask,
+                              prev->streams_index_mask, 32 - 1);
+
+    ngx_conf_merge_msec_value(conf->recv_timeout,
+                              prev->recv_timeout, 30000);
+    ngx_conf_merge_msec_value(conf->keepalive_timeout,
+                              prev->keepalive_timeout, 180000);
+
+    ngx_conf_merge_value(conf->headers_comp, prev->headers_comp,
+                         Z_NO_COMPRESSION);
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_spdy_module_init(ngx_conf_t *cf)
+{
+    ngx_http_spdy_main_conf_t  *smcf;
+
+    smcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_spdy_module);
+
+    if (smcf->enable) {
+        smcf->recv_buffer = ngx_palloc(cf->pool, smcf->recv_buffer_size);
+        if (smcf->recv_buffer == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_http_spdy_request_headers_init();
+    }
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_http_spdy_streams_index_mask(ngx_conf_t *cf, void *post, void *data)
+{
+    ngx_uint_t *np = data;
+
+    ngx_uint_t  mask;
+
+    mask = *np - 1;
+
+    if (*np & mask) {
+        return "must be a power of two";
+    }
+
+    *np = mask;
+
+    return NGX_CONF_OK;
+}
diff -r 7ecddb37e4c8 -r 0fed3becc056 src/http/ngx_http_spdy_module.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/http/ngx_http_spdy_module.h	Wed Mar 13 02:58:14 2013 +0400
@@ -0,0 +1,37 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Valentin V. Bartenev
+ */
+
+
+#ifndef _NGX_HTTP_SPDY_MODULE_H_INCLUDED_
+#define _NGX_HTTP_SPDY_MODULE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_flag_t                      enable;
+    size_t                          recv_buffer_size;
+    u_char                         *recv_buffer;
+} ngx_http_spdy_main_conf_t;
+
+
+typedef struct {
+    size_t                          pool_size;
+    ngx_uint_t                      concurrent_streams;
+    ngx_uint_t                      streams_index_mask;
+    ngx_msec_t                      recv_timeout;
+    ngx_msec_t                      keepalive_timeout;
+    ngx_int_t                       headers_comp;
+} ngx_http_spdy_srv_conf_t;
+
+
+extern ngx_module_t  ngx_http_spdy_module;
+
+
+#endif /* _NGX_HTTP_SPDY_MODULE_H_INCLUDED_ */
diff -r 7ecddb37e4c8 -r 0fed3becc056 src/http/ngx_http_upstream.c
--- a/src/http/ngx_http_upstream.c	Mon Mar 11 18:22:11 2013 +0400
+++ b/src/http/ngx_http_upstream.c	Wed Mar 13 02:58:14 2013 +0400
@@ -440,6 +440,13 @@ ngx_http_upstream_init(ngx_http_request_
     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
                    "http init upstream, client timer: %d", c->read->timer_set);
 
+#if (NGX_HTTP_SPDY)
+    if (r->spdy_stream) {
+        ngx_http_upstream_init_request(r);
+        return;
+    }
+#endif
+
     if (c->read->timer_set) {
         ngx_del_timer(c->read);
     }
@@ -1012,6 +1019,12 @@ ngx_http_upstream_check_broken_connectio
         return;
     }
 
+#if (NGX_HTTP_SPDY)
+    if (r->spdy_stream) {
+        return;
+    }
+#endif
+
 #if (NGX_HAVE_KQUEUE)
 
     if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
