diff -r ce946e0976dc -r edacc9766e8e src/http/ngx_http_request.h
--- a/src/http/ngx_http_request.h	Thu Feb 25 16:29:51 2016 +0300
+++ b/src/http/ngx_http_request.h	Fri Feb 26 18:02:02 2016 +0300
@@ -284,6 +284,9 @@ typedef struct {
     ngx_chain_t                      *bufs;
     ngx_buf_t                        *buf;
     off_t                             rest;
+#if (NGX_HTTP_V2)
+    off_t                             received;
+#endif
     ngx_chain_t                      *free;
     ngx_chain_t                      *busy;
     ngx_http_chunked_t               *chunked;
diff -r ce946e0976dc -r edacc9766e8e src/http/ngx_http_request_body.c
--- a/src/http/ngx_http_request_body.c	Thu Feb 25 16:29:51 2016 +0300
+++ b/src/http/ngx_http_request_body.c	Fri Feb 26 18:02:02 2016 +0300
@@ -34,35 +34,31 @@ ngx_http_read_client_request_body(ngx_ht
     ssize_t                    size;
     ngx_int_t                  rc;
     ngx_buf_t                 *b;
-    ngx_chain_t                out, *cl;
+    ngx_chain_t                out;
     ngx_http_request_body_t   *rb;
     ngx_http_core_loc_conf_t  *clcf;
 
     r->main->count++;
 
-#if (NGX_HTTP_V2)
-    if (r->stream && r == r->main) {
-        r->request_body_no_buffering = 0;
-        rc = ngx_http_v2_read_request_body(r, post_handler);
-        goto done;
-    }
-#endif
-
     if (r != r->main || r->request_body || r->discard_body) {
         r->request_body_no_buffering = 0;
         post_handler(r);
         return NGX_OK;
     }
 
+#if (NGX_HTTP_V2)
+    if (r->stream) {
+        r->request_body_no_buffering = 0;
+        rc = ngx_http_v2_read_request_body(r, post_handler);
+        goto done;
+    }
+#endif
+
     if (ngx_http_test_expect(r) != NGX_OK) {
         rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
         goto done;
     }
 
-    if (r->request_body_no_buffering) {
-        r->request_body_in_file_only = 0;
-    }
-
     rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t));
     if (rb == NULL) {
         rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
@@ -148,37 +144,8 @@ ngx_http_read_client_request_body(ngx_ht
 
     if (rb->rest == 0) {
         /* the whole request body was pre-read */
-
-        if (r->request_body_in_file_only) {
-            if (ngx_http_write_request_body(r) != NGX_OK) {
-                rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
-                goto done;
-            }
-
-            if (rb->temp_file->file.offset != 0) {
-
-                cl = ngx_chain_get_free_buf(r->pool, &rb->free);
-                if (cl == NULL) {
-                    rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
-                    goto done;
-                }
-
-                b = cl->buf;
-
-                ngx_memzero(b, sizeof(ngx_buf_t));
-
-                b->in_file = 1;
-                b->file_last = rb->temp_file->file.offset;
-                b->file = &rb->temp_file->file;
-
-                rb->bufs = cl;
-            }
-        }
-
         r->request_body_no_buffering = 0;
-
         post_handler(r);
-
         return NGX_OK;
     }
 
@@ -289,8 +256,7 @@ ngx_http_do_read_client_request_body(ngx
     size_t                     size;
     ssize_t                    n;
     ngx_int_t                  rc;
-    ngx_buf_t                 *b;
-    ngx_chain_t               *cl, out;
+    ngx_chain_t                out;
     ngx_connection_t          *c;
     ngx_http_request_body_t   *rb;
     ngx_http_core_loc_conf_t  *clcf;
@@ -439,33 +405,6 @@ ngx_http_do_read_client_request_body(ngx
         ngx_del_timer(c->read);
     }
 
-    if (rb->temp_file || r->request_body_in_file_only) {
-
-        /* save the last part */
-
-        if (ngx_http_write_request_body(r) != NGX_OK) {
-            return NGX_HTTP_INTERNAL_SERVER_ERROR;
-        }
-
-        if (rb->temp_file->file.offset != 0) {
-
-            cl = ngx_chain_get_free_buf(r->pool, &rb->free);
-            if (cl == NULL) {
-                return NGX_HTTP_INTERNAL_SERVER_ERROR;
-            }
-
-            b = cl->buf;
-
-            ngx_memzero(b, sizeof(ngx_buf_t));
-
-            b->in_file = 1;
-            b->file_last = rb->temp_file->file.offset;
-            b->file = &rb->temp_file->file;
-
-            rb->bufs = cl;
-        }
-    }
-
     if (!r->request_body_no_buffering) {
         r->read_event_handler = ngx_http_block_reading;
         rb->post_handler(r);
@@ -564,17 +503,17 @@ ngx_http_discard_request_body(ngx_http_r
     ngx_int_t     rc;
     ngx_event_t  *rev;
 
+    if (r != r->main || r->discard_body || r->request_body) {
+        return NGX_OK;
+    }
+
 #if (NGX_HTTP_V2)
-    if (r->stream && r == r->main) {
-        r->stream->skip_data = NGX_HTTP_V2_DATA_DISCARD;
+    if (r->stream) {
+        r->stream->skip_data = 1;
         return NGX_OK;
     }
 #endif
 
-    if (r != r->main || r->discard_body || r->request_body) {
-        return NGX_OK;
-    }
-
     if (ngx_http_test_expect(r) != NGX_OK) {
         return NGX_HTTP_INTERNAL_SERVER_ERROR;
     }
@@ -1127,9 +1066,8 @@ ngx_http_request_body_chunked_filter(ngx
 ngx_int_t
 ngx_http_request_body_save_filter(ngx_http_request_t *r, ngx_chain_t *in)
 {
-#if (NGX_DEBUG)
+    ngx_buf_t                 *b;
     ngx_chain_t               *cl;
-#endif
     ngx_http_request_body_t   *rb;
 
     rb = r->request_body;
@@ -1166,13 +1104,46 @@ ngx_http_request_body_save_filter(ngx_ht
         return NGX_HTTP_INTERNAL_SERVER_ERROR;
     }
 
-    if (rb->rest > 0
-        && rb->buf && rb->buf->last == rb->buf->end
-        && !r->request_body_no_buffering)
-    {
+    if (r->request_body_no_buffering) {
+        return NGX_OK;
+    }
+
+    if (rb->rest > 0) {
+
+        if (rb->buf && rb->buf->last == rb->buf->end
+            && ngx_http_write_request_body(r) != NGX_OK)
+        {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        return NGX_OK;
+    }
+
+    /* rb->rest == 0 */
+
+    if (rb->temp_file || r->request_body_in_file_only) {
+
         if (ngx_http_write_request_body(r) != NGX_OK) {
             return NGX_HTTP_INTERNAL_SERVER_ERROR;
         }
+
+        if (rb->temp_file->file.offset != 0) {
+
+            cl = ngx_chain_get_free_buf(r->pool, &rb->free);
+            if (cl == NULL) {
+                return NGX_HTTP_INTERNAL_SERVER_ERROR;
+            }
+
+            b = cl->buf;
+
+            ngx_memzero(b, sizeof(ngx_buf_t));
+
+            b->in_file = 1;
+            b->file_last = rb->temp_file->file.offset;
+            b->file = &rb->temp_file->file;
+
+            rb->bufs = cl;
+        }
     }
 
     return NGX_OK;
diff -r ce946e0976dc -r edacc9766e8e src/http/v2/ngx_http_v2.c
--- a/src/http/v2/ngx_http_v2.c	Thu Feb 25 16:29:51 2016 +0300
+++ b/src/http/v2/ngx_http_v2.c	Fri Feb 26 18:02:02 2016 +0300
@@ -51,6 +51,8 @@
 #define NGX_HTTP_V2_MAX_WINDOW                   ((1U << 31) - 1)
 #define NGX_HTTP_V2_DEFAULT_WINDOW               65535
 
+#define NGX_HTTP_V2_INITIAL_WINDOW               0
+
 #define NGX_HTTP_V2_ROOT                         (void *) -1
 
 
@@ -163,7 +165,10 @@ static ngx_int_t ngx_http_v2_cookie(ngx_
     ngx_http_v2_header_t *header);
 static ngx_int_t ngx_http_v2_construct_cookie_header(ngx_http_request_t *r);
 static void ngx_http_v2_run_request(ngx_http_request_t *r);
-static ngx_int_t ngx_http_v2_init_request_body(ngx_http_request_t *r);
+static ngx_int_t ngx_http_v2_account_request_body(ngx_http_request_t *r,
+    size_t size, ngx_uint_t last);
+static ngx_int_t ngx_http_v2_process_request_body(ngx_http_request_t *r,
+    u_char *pos, size_t size, ngx_uint_t last);
 static void ngx_http_v2_read_client_request_body_handler(ngx_http_request_t *r);
 
 static ngx_int_t ngx_http_v2_terminate_stream(ngx_http_v2_connection_t *h2c,
@@ -754,6 +759,7 @@ ngx_http_v2_state_head(ngx_http_v2_conne
 static u_char *
 ngx_http_v2_state_data(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end)
 {
+    ngx_int_t              rc;
     ngx_http_v2_node_t    *node;
     ngx_http_v2_stream_t  *stream;
 
@@ -845,8 +851,9 @@ ngx_http_v2_state_data(ngx_http_v2_conne
 
     stream->recv_window -= h2c->state.length;
 
-    if (stream->recv_window < NGX_HTTP_V2_MAX_WINDOW / 4) {
-
+    if (stream->no_flow_control
+        && stream->recv_window < NGX_HTTP_V2_MAX_WINDOW / 4)
+    {
         if (ngx_http_v2_send_window_update(h2c, node->id,
                                            NGX_HTTP_V2_MAX_WINDOW
                                            - stream->recv_window)
@@ -875,6 +882,16 @@ ngx_http_v2_state_data(ngx_http_v2_conne
         return ngx_http_v2_state_skip_padded(h2c, pos, end);
     }
 
+    stream->in_closed = h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG;
+
+    rc = ngx_http_v2_account_request_body(stream->request, h2c->state.length,
+                                          stream->in_closed);
+    if (rc != NGX_OK) {
+        stream->skip_data = 1;
+        ngx_http_finalize_request(stream->request, rc);
+        return ngx_http_v2_state_skip_padded(h2c, pos, end);
+    }
+
     h2c->state.stream = stream;
 
     return ngx_http_v2_state_read_data(h2c, pos, end);
@@ -885,16 +902,10 @@ static u_char *
 ngx_http_v2_state_read_data(ngx_http_v2_connection_t *h2c, u_char *pos,
     u_char *end)
 {
-    size_t                     size;
-    ssize_t                    n;
-    ngx_buf_t                 *buf;
-    ngx_int_t                  rc;
-    ngx_temp_file_t           *tf;
-    ngx_connection_t          *fc;
-    ngx_http_request_t        *r;
-    ngx_http_v2_stream_t      *stream;
-    ngx_http_request_body_t   *rb;
-    ngx_http_core_loc_conf_t  *clcf;
+    size_t                 size;
+    ngx_int_t              rc;
+    ngx_uint_t             last;
+    ngx_http_v2_stream_t  *stream;
 
     stream = h2c->state.stream;
 
@@ -903,168 +914,39 @@ ngx_http_v2_state_read_data(ngx_http_v2_
     }
 
     if (stream->skip_data) {
-        stream->in_closed = h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG;
-
-        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
-                       "skipping http2 DATA frame, reason: %d",
-                       stream->skip_data);
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
+                       "skipping http2 DATA frame");
 
         return ngx_http_v2_state_skip_padded(h2c, pos, end);
     }
 
+    last = 0;
     size = end - pos;
 
-    if (size > h2c->state.length) {
+    if (size >= h2c->state.length) {
         size = h2c->state.length;
-    }
-
-    r = stream->request;
-
-    if (r->request_body == NULL
-        && ngx_http_v2_init_request_body(r) != NGX_OK)
-    {
-        stream->skip_data = NGX_HTTP_V2_DATA_INTERNAL_ERROR;
-        return ngx_http_v2_state_skip_padded(h2c, pos, end);
-    }
-
-    fc = r->connection;
-    rb = r->request_body;
-    tf = rb->temp_file;
-    buf = rb->buf;
-
-    if (size) {
-        rb->rest += size;
-
-        if (r->headers_in.content_length_n != -1
-            && r->headers_in.content_length_n < rb->rest)
-        {
-            ngx_log_error(NGX_LOG_INFO, fc->log, 0,
-                          "client intended to send body data "
-                          "larger than declared");
-
-            stream->skip_data = NGX_HTTP_V2_DATA_ERROR;
-            goto error;
-
-        } else {
-            clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
-
-            if (clcf->client_max_body_size
-                && clcf->client_max_body_size < rb->rest)
-            {
-                ngx_log_error(NGX_LOG_ERR, fc->log, 0,
-                              "client intended to send "
-                              "too large chunked body: %O bytes", rb->rest);
-
-                stream->skip_data = NGX_HTTP_V2_DATA_ERROR;
-                goto error;
-            }
-        }
-
-        h2c->state.length -= size;
-
-        if (tf) {
-            buf->start = pos;
-            buf->pos = pos;
-
-            pos += size;
-
-            buf->end = pos;
-            buf->last = pos;
-
-            n = ngx_write_chain_to_temp_file(tf, rb->bufs);
-
-            /* TODO: n == 0 or not complete and level event */
-
-            if (n == NGX_ERROR) {
-                stream->skip_data = NGX_HTTP_V2_DATA_INTERNAL_ERROR;
-                goto error;
-            }
-
-            tf->offset += n;
-
-        } else {
-            buf->last = ngx_cpymem(buf->last, pos, size);
-            pos += size;
-        }
-
-        r->request_length += size;
-    }
+        last = stream->in_closed;
+    }
+
+    rc = ngx_http_v2_process_request_body(stream->request, pos, size, last);
+    if (rc != NGX_OK) {
+        stream->skip_data = 1;
+        ngx_http_finalize_request(stream->request, rc);
+    }
+
+    pos += size;
+    h2c->state.length -= size;
 
     if (h2c->state.length) {
-        if (rb->post_handler) {
-            clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
-            ngx_add_timer(fc->read, clcf->client_body_timeout);
-        }
-
         return ngx_http_v2_state_save(h2c, pos, end,
                                       ngx_http_v2_state_read_data);
     }
 
-    if (h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG) {
-        stream->in_closed = 1;
-
-        if (r->headers_in.content_length_n < 0) {
-            r->headers_in.content_length_n = rb->rest;
-
-        } else if (r->headers_in.content_length_n != rb->rest) {
-            ngx_log_error(NGX_LOG_INFO, fc->log, 0,
-                          "client prematurely closed stream: "
-                          "only %O out of %O bytes of request body received",
-                          rb->rest, r->headers_in.content_length_n);
-
-            stream->skip_data = NGX_HTTP_V2_DATA_ERROR;
-            goto error;
-        }
-
-        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) {
-            if (fc->read->timer_set) {
-                ngx_del_timer(fc->read);
-            }
-
-            r->read_event_handler = ngx_http_block_reading;
-            rb->post_handler(r);
-        }
-
-    } else if (rb->post_handler) {
-        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
-        ngx_add_timer(fc->read, clcf->client_body_timeout);
-    }
-
     if (h2c->state.padding) {
         return ngx_http_v2_state_skip_padded(h2c, pos, end);
     }
 
     return ngx_http_v2_state_complete(h2c, pos, end);
-
-error:
-
-    if (rb->post_handler) {
-        if (fc->read->timer_set) {
-            ngx_del_timer(fc->read);
-        }
-
-        if (stream->skip_data == NGX_HTTP_V2_DATA_ERROR) {
-            rc = (r->headers_in.content_length_n == -1)
-                 ? NGX_HTTP_REQUEST_ENTITY_TOO_LARGE : NGX_HTTP_BAD_REQUEST;
-
-        } else {
-            rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
-        }
-
-        ngx_http_finalize_request(r, rc);
-    }
-
-    return ngx_http_v2_state_skip_padded(h2c, pos, end);
 }
 
 
@@ -2556,7 +2438,7 @@ ngx_http_v2_send_settings(ngx_http_v2_co
         buf->last = ngx_http_v2_write_uint16(buf->last,
                                          NGX_HTTP_V2_INIT_WINDOW_SIZE_SETTING);
         buf->last = ngx_http_v2_write_uint32(buf->last,
-                                             NGX_HTTP_V2_MAX_WINDOW);
+                                             NGX_HTTP_V2_INITIAL_WINDOW);
 
         buf->last = ngx_http_v2_write_uint16(buf->last,
                                            NGX_HTTP_V2_MAX_FRAME_SIZE_SETTING);
@@ -2867,7 +2749,7 @@ ngx_http_v2_create_stream(ngx_http_v2_co
     stream->connection = h2c;
 
     stream->send_window = h2c->init_window;
-    stream->recv_window = NGX_HTTP_V2_MAX_WINDOW;
+    stream->recv_window = NGX_HTTP_V2_INITIAL_WINDOW;
 
     h2c->processing++;
 
@@ -3504,7 +3386,7 @@ ngx_http_v2_run_request(ngx_http_request
         ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                       "client prematurely closed stream");
 
-        r->stream->skip_data = NGX_HTTP_V2_DATA_ERROR;
+        r->stream->skip_data = 1;
 
         ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
         return;
@@ -3514,142 +3396,241 @@ ngx_http_v2_run_request(ngx_http_request
 }
 
 
-static ngx_int_t
-ngx_http_v2_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->stream->in_closed) {
-        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
-        || rb->rest < 0)
-    {
-        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->stream->in_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;
-        }
-
-    } 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;
-
-    rb->rest = 0;
-
-    return NGX_OK;
-}
-
-
 ngx_int_t
 ngx_http_v2_read_request_body(ngx_http_request_t *r,
     ngx_http_client_body_handler_pt post_handler)
 {
+    off_t                      len;
     ngx_http_v2_stream_t      *stream;
+    ngx_http_request_body_t   *rb;
     ngx_http_core_loc_conf_t  *clcf;
 
-    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
-                   "http2 read request body");
-
     stream = r->stream;
 
-    switch (stream->skip_data) {
-
-    case NGX_HTTP_V2_DATA_DISCARD:
-        post_handler(r);
+    rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t));
+    if (rb == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     rb->bufs = NULL;
+     *     rb->buf = NULL;
+     *     rb->received = 0;
+     *     rb->free = NULL;
+     *     rb->busy = NULL;
+     */
+
+    rb->post_handler = post_handler;
+
+    r->request_body = rb;
+
+    if (stream->skip_data) {
+        return NGX_HTTP_BAD_REQUEST;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    len = r->headers_in.content_length_n;
+
+    if (stream->in_closed && len < 0) {
+        len = 0;
+    }
+
+    if (len != 0) {
+        if (len < 0
+            || len > (off_t) clcf->client_body_buffer_size
+            || r->request_body_in_file_only)
+        {
+            rb->buf = ngx_calloc_buf(r->pool);
+
+            if (rb->buf != NULL) {
+                rb->buf->memory = 1;
+                rb->buf->sync = 1;
+            }
+
+        } else {
+            rb->buf = ngx_create_temp_buf(r->pool, (size_t) len);
+        }
+
+        if (rb->buf == NULL) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+    }
+
+    if (stream->in_closed) {
+        return ngx_http_v2_process_request_body(r, NULL, 0, 1);
+    }
+
+    stream->recv_window = NGX_HTTP_V2_MAX_WINDOW;
+
+    if (ngx_http_v2_send_window_update(stream->connection, stream->node->id,
+                                       stream->recv_window)
+        == NGX_ERROR)
+    {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    stream->no_flow_control = 1;
+
+    ngx_add_timer(r->connection->read, clcf->client_body_timeout);
+
+    r->read_event_handler = ngx_http_v2_read_client_request_body_handler;
+    r->write_event_handler = ngx_http_request_empty_handler;
+
+    return NGX_AGAIN;
+}
+
+
+static ngx_int_t
+ngx_http_v2_account_request_body(ngx_http_request_t *r, size_t size,
+    ngx_uint_t last)
+{
+    ngx_http_request_body_t   *rb;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    rb = r->request_body;
+
+    if (rb == NULL) {
         return NGX_OK;
-
-    case NGX_HTTP_V2_DATA_ERROR:
-        if (r->headers_in.content_length_n == -1) {
-            return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE;
-        } else {
+    }
+
+    if (size) {
+        r->request_length += size;
+        rb->received += size;
+
+        if (r->headers_in.content_length_n != -1
+            && r->headers_in.content_length_n < rb->received)
+        {
+            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                          "client intended to send body data "
+                          "larger than declared");
+
             return NGX_HTTP_BAD_REQUEST;
         }
 
-    case NGX_HTTP_V2_DATA_INTERNAL_ERROR:
-        return NGX_HTTP_INTERNAL_SERVER_ERROR;
-    }
-
-    if (!r->request_body && ngx_http_v2_init_request_body(r) != NGX_OK) {
-        stream->skip_data = NGX_HTTP_V2_DATA_INTERNAL_ERROR;
-        return NGX_HTTP_INTERNAL_SERVER_ERROR;
-    }
-
-    if (stream->in_closed) {
-        post_handler(r);
+        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+        if (clcf->client_max_body_size
+            && clcf->client_max_body_size < rb->received)
+        {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                         "client intended to send too large chunked body: "
+                         "%O bytes", rb->received);
+
+            return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE;
+        }
+    }
+
+    if (last) {
+        if (r->headers_in.content_length_n != -1
+            && r->headers_in.content_length_n != rb->received)
+        {
+            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                          "client prematurely closed stream: "
+                          "only %O out of %O bytes of request body received",
+                          rb->received, r->headers_in.content_length_n);
+
+            return NGX_HTTP_BAD_REQUEST;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v2_process_request_body(ngx_http_request_t *r, u_char *pos,
+    size_t size, ngx_uint_t last)
+{
+    ngx_buf_t                 *buf;
+    ngx_int_t                  rc;
+    ngx_chain_t                out;
+    ngx_connection_t          *fc;
+    ngx_http_request_body_t   *rb;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    fc = r->connection;
+    rb = r->request_body;
+
+    if (rb == NULL) {
         return NGX_OK;
     }
 
-    r->request_body->post_handler = post_handler;
-
-    r->read_event_handler = ngx_http_v2_read_client_request_body_handler;
-    r->write_event_handler = ngx_http_request_empty_handler;
+    rb->rest = !last;
+
+    buf = rb->buf;
+
+    if (size) {
+        if (buf->sync) {
+            buf->start = pos;
+            buf->pos = pos;
+
+            pos += size;
+
+            buf->end = pos;
+            buf->last = pos;
+
+            buf->last_buf = last;
+
+            out.buf = buf;
+            out.next = NULL;
+
+            rc = ngx_http_top_request_body_filter(r, &out);
+
+            if (rc != NGX_OK) {
+                return rc;
+            }
+
+        } else {
+            buf->last = ngx_cpymem(buf->last, pos, size);
+        }
+    }
+
+    if (last) {
+        if (r->headers_in.content_length_n < 0) {
+            r->headers_in.content_length_n = rb->received;
+        }
+
+        if (buf) {
+            if (buf->sync) {
+                /* prevent reusing this buffer in the upstream module */
+                rb->buf = NULL;
+
+                rc = size ? NGX_OK : ngx_http_top_request_body_filter(r, NULL);
+
+            } else {
+                buf->last_buf = 1;
+
+                out.buf = buf;
+                out.next = NULL;
+
+                rc = ngx_http_top_request_body_filter(r, &out);
+            }
+
+        } else {
+            rc = ngx_http_top_request_body_filter(r, NULL);
+        }
+
+        if (rc != NGX_OK) {
+            return rc;
+        }
+
+        if (fc->read->timer_set) {
+            ngx_del_timer(fc->read);
+        }
+
+        r->read_event_handler = ngx_http_block_reading;
+        rb->post_handler(r);
+
+        return NGX_OK;
+    }
 
     clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
-    ngx_add_timer(r->connection->read, clcf->client_body_timeout);
-
-    return NGX_AGAIN;
+    ngx_add_timer(fc->read, clcf->client_body_timeout);
+
+    return NGX_OK;
 }
 
 
@@ -3667,7 +3648,7 @@ ngx_http_v2_read_client_request_body_han
         ngx_log_error(NGX_LOG_INFO, fc->log, NGX_ETIMEDOUT, "client timed out");
 
         fc->timedout = 1;
-        r->stream->skip_data = NGX_HTTP_V2_DATA_DISCARD;
+        r->stream->skip_data = 1;
 
         ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT);
         return;
@@ -3677,7 +3658,7 @@ ngx_http_v2_read_client_request_body_han
         ngx_log_error(NGX_LOG_INFO, fc->log, 0,
                       "client prematurely closed stream");
 
-        r->stream->skip_data = NGX_HTTP_V2_DATA_DISCARD;
+        r->stream->skip_data = 1;
 
         ngx_http_finalize_request(r, NGX_HTTP_CLIENT_CLOSED_REQUEST);
         return;
diff -r ce946e0976dc -r edacc9766e8e src/http/v2/ngx_http_v2.h
--- a/src/http/v2/ngx_http_v2.h	Thu Feb 25 16:29:51 2016 +0300
+++ b/src/http/v2/ngx_http_v2.h	Fri Feb 26 18:02:02 2016 +0300
@@ -194,7 +194,8 @@ struct ngx_http_v2_stream_s {
     unsigned                         exhausted:1;
     unsigned                         in_closed:1;
     unsigned                         out_closed:1;
-    unsigned                         skip_data:2;
+    unsigned                         no_flow_control:1;
+    unsigned                         skip_data:1;
 };
 
 
