changeset 6052:8ad78808a612

FastCGI: fastcgi_request_buffering.
author Maxim Dounin <mdounin@mdounin.ru>
date Mon, 23 Mar 2015 21:09:19 +0300
parents d97e6be2d292
children b6eb6ec4fbd9
files src/http/modules/ngx_http_fastcgi_module.c
diffstat 1 files changed, 343 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/src/http/modules/ngx_http_fastcgi_module.c
+++ b/src/http/modules/ngx_http_fastcgi_module.c
@@ -81,8 +81,12 @@ typedef struct {
     size_t                         length;
     size_t                         padding;
 
+    ngx_chain_t                   *free;
+    ngx_chain_t                   *busy;
+
     unsigned                       fastcgi_stdout:1;
     unsigned                       large_stderr:1;
+    unsigned                       header_sent:1;
 
     ngx_array_t                   *split_parts;
 
@@ -147,6 +151,8 @@ static ngx_int_t ngx_http_fastcgi_create
 #endif
 static ngx_int_t ngx_http_fastcgi_create_request(ngx_http_request_t *r);
 static ngx_int_t ngx_http_fastcgi_reinit_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_fastcgi_body_output_filter(void *data,
+    ngx_chain_t *in);
 static ngx_int_t ngx_http_fastcgi_process_header(ngx_http_request_t *r);
 static ngx_int_t ngx_http_fastcgi_input_filter_init(void *data);
 static ngx_int_t ngx_http_fastcgi_input_filter(ngx_event_pipe_t *p,
@@ -257,6 +263,13 @@ static ngx_command_t  ngx_http_fastcgi_c
       offsetof(ngx_http_fastcgi_loc_conf_t, upstream.buffering),
       NULL },
 
+    { ngx_string("fastcgi_request_buffering"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.request_buffering),
+      NULL },
+
     { ngx_string("fastcgi_ignore_client_abort"),
       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
       ngx_conf_set_flag_slot,
@@ -703,6 +716,12 @@ ngx_http_fastcgi_handler(ngx_http_reques
     u->input_filter = ngx_http_fastcgi_non_buffered_filter;
     u->input_filter_ctx = r;
 
+    if (!flcf->upstream.request_buffering
+        && flcf->upstream.pass_request_body)
+    {
+        r->request_body_no_buffering = 1;
+    }
+
     rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);
 
     if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
@@ -799,6 +818,7 @@ ngx_http_fastcgi_create_request(ngx_http
     ngx_chain_t                  *cl, *body;
     ngx_list_part_t              *part;
     ngx_table_elt_t              *header, **ignored;
+    ngx_http_upstream_t          *u;
     ngx_http_script_code_pt       code;
     ngx_http_script_engine_t      e, le;
     ngx_http_fastcgi_header_t    *h;
@@ -810,10 +830,12 @@ ngx_http_fastcgi_create_request(ngx_http
     header_params = 0;
     ignored = NULL;
 
+    u = r->upstream;
+
     flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module);
 
 #if (NGX_HTTP_CACHE)
-    params = r->upstream->cacheable ? &flcf->params_cache : &flcf->params;
+    params = u->cacheable ? &flcf->params_cache : &flcf->params;
 #else
     params = &flcf->params;
 #endif
@@ -1134,12 +1156,17 @@ ngx_http_fastcgi_create_request(ngx_http
     h->padding_length = 0;
     h->reserved = 0;
 
-    h = (ngx_http_fastcgi_header_t *) b->last;
-    b->last += sizeof(ngx_http_fastcgi_header_t);
-
-    if (flcf->upstream.pass_request_body) {
-        body = r->upstream->request_bufs;
-        r->upstream->request_bufs = cl;
+    if (r->request_body_no_buffering) {
+
+        u->request_bufs = cl;
+
+        u->output.output_filter = ngx_http_fastcgi_body_output_filter;
+        u->output.filter_ctx = r;
+
+    } else if (flcf->upstream.pass_request_body) {
+
+        body = u->request_bufs;
+        u->request_bufs = cl;
 
 #if (NGX_SUPPRESS_WARN)
         file_pos = 0;
@@ -1194,6 +1221,9 @@ ngx_http_fastcgi_create_request(ngx_http
                 padding = 8 - len % 8;
                 padding = (padding == 8) ? 0 : padding;
 
+                h = (ngx_http_fastcgi_header_t *) cl->buf->last;
+                cl->buf->last += sizeof(ngx_http_fastcgi_header_t);
+
                 h->version = 1;
                 h->type = NGX_HTTP_FASTCGI_STDIN;
                 h->request_id_hi = 0;
@@ -1223,9 +1253,6 @@ ngx_http_fastcgi_create_request(ngx_http
                     b->last += padding;
                 }
 
-                h = (ngx_http_fastcgi_header_t *) b->last;
-                b->last += sizeof(ngx_http_fastcgi_header_t);
-
                 cl->next = ngx_alloc_chain_link(r->pool);
                 if (cl->next == NULL) {
                     return NGX_ERROR;
@@ -1240,17 +1267,22 @@ ngx_http_fastcgi_create_request(ngx_http
         }
 
     } else {
-        r->upstream->request_bufs = cl;
+        u->request_bufs = cl;
     }
 
-    h->version = 1;
-    h->type = NGX_HTTP_FASTCGI_STDIN;
-    h->request_id_hi = 0;
-    h->request_id_lo = 1;
-    h->content_length_hi = 0;
-    h->content_length_lo = 0;
-    h->padding_length = 0;
-    h->reserved = 0;
+    if (!r->request_body_no_buffering) {
+        h = (ngx_http_fastcgi_header_t *) cl->buf->last;
+        cl->buf->last += sizeof(ngx_http_fastcgi_header_t);
+
+        h->version = 1;
+        h->type = NGX_HTTP_FASTCGI_STDIN;
+        h->request_id_hi = 0;
+        h->request_id_lo = 1;
+        h->content_length_hi = 0;
+        h->content_length_lo = 0;
+        h->padding_length = 0;
+        h->reserved = 0;
+    }
 
     cl->next = NULL;
 
@@ -1284,6 +1316,294 @@ ngx_http_fastcgi_reinit_request(ngx_http
 
 
 static ngx_int_t
+ngx_http_fastcgi_body_output_filter(void *data, ngx_chain_t *in)
+{
+    ngx_http_request_t  *r = data;
+
+    off_t                       file_pos;
+    u_char                     *pos, *start;
+    size_t                      len, padding;
+    ngx_buf_t                  *b;
+    ngx_int_t                   rc;
+    ngx_uint_t                  next, last;
+    ngx_chain_t                *cl, *tl, *out, **ll;
+    ngx_http_fastcgi_ctx_t     *f;
+    ngx_http_fastcgi_header_t  *h;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "fastcgi output filter");
+
+    f = ngx_http_get_module_ctx(r, ngx_http_fastcgi_module);
+
+    if (in == NULL) {
+        out = in;
+        goto out;
+    }
+
+    out = NULL;
+    ll = &out;
+
+    if (!f->header_sent) {
+        /* first buffer contains headers, pass it unmodified */
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "fastcgi output header");
+
+        f->header_sent = 1;
+
+        tl = ngx_alloc_chain_link(r->pool);
+        if (tl == NULL) {
+            return NGX_ERROR;
+        }
+
+        tl->buf = in->buf;
+        *ll = tl;
+        ll = &tl->next;
+
+        in = in->next;
+
+        if (in == NULL) {
+            tl->next = NULL;
+            goto out;
+        }
+    }
+
+    cl = ngx_chain_get_free_buf(r->pool, &f->free);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    b = cl->buf;
+
+    b->tag = (ngx_buf_tag_t) &ngx_http_fastcgi_body_output_filter;
+    b->temporary = 1;
+
+    if (b->start == NULL) {
+        /* reserve space for maximum possible padding, 7 bytes */
+
+        b->start = ngx_palloc(r->pool,
+                              sizeof(ngx_http_fastcgi_header_t) + 7);
+        if (b->start == NULL) {
+            return NGX_ERROR;
+        }
+
+        b->pos = b->start;
+        b->last = b->start;
+
+        b->end = b->start + sizeof(ngx_http_fastcgi_header_t) + 7;
+    }
+
+    *ll = cl;
+
+    last = 0;
+    padding = 0;
+
+#if (NGX_SUPPRESS_WARN)
+    file_pos = 0;
+    pos = NULL;
+#endif
+
+    while (in) {
+
+        ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0,
+                       "fastcgi output in  l:%d f:%d %p, pos %p, size: %z "
+                       "file: %O, size: %O",
+                       in->buf->last_buf,
+                       in->buf->in_file,
+                       in->buf->start, in->buf->pos,
+                       in->buf->last - in->buf->pos,
+                       in->buf->file_pos,
+                       in->buf->file_last - in->buf->file_pos);
+
+        if (in->buf->last_buf) {
+            last = 1;
+        }
+
+        if (ngx_buf_special(in->buf)) {
+            in = in->next;
+            continue;
+        }
+
+        if (in->buf->in_file) {
+            file_pos = in->buf->file_pos;
+
+        } else {
+            pos = in->buf->pos;
+        }
+
+        next = 0;
+
+        do {
+            tl = ngx_chain_get_free_buf(r->pool, &f->free);
+            if (tl == NULL) {
+                return NGX_ERROR;
+            }
+
+            b = tl->buf;
+            start = b->start;
+
+            ngx_memcpy(b, in->buf, sizeof(ngx_buf_t));
+
+            /*
+             * restore b->start to preserve memory allocated in the buffer,
+             * to reuse it later for headers and padding
+             */
+
+            b->start = start;
+
+            if (in->buf->in_file) {
+                b->file_pos = file_pos;
+                file_pos += 32 * 1024;
+
+                if (file_pos >= in->buf->file_last) {
+                    file_pos = in->buf->file_last;
+                    next = 1;
+                }
+
+                b->file_last = file_pos;
+                len = (ngx_uint_t) (file_pos - b->file_pos);
+
+            } else {
+                b->pos = pos;
+                pos += 32 * 1024;
+
+                if (pos >= in->buf->last) {
+                    pos = in->buf->last;
+                    next = 1;
+                }
+
+                b->last = pos;
+                len = (ngx_uint_t) (pos - b->pos);
+            }
+
+            b->tag = (ngx_buf_tag_t) &ngx_http_fastcgi_body_output_filter;
+            b->shadow = in->buf;
+            b->last_shadow = next;
+
+            b->last_buf = 0;
+            b->last_in_chain = 0;
+
+            padding = 8 - len % 8;
+            padding = (padding == 8) ? 0 : padding;
+
+            h = (ngx_http_fastcgi_header_t *) cl->buf->last;
+            cl->buf->last += sizeof(ngx_http_fastcgi_header_t);
+
+            h->version = 1;
+            h->type = NGX_HTTP_FASTCGI_STDIN;
+            h->request_id_hi = 0;
+            h->request_id_lo = 1;
+            h->content_length_hi = (u_char) ((len >> 8) & 0xff);
+            h->content_length_lo = (u_char) (len & 0xff);
+            h->padding_length = (u_char) padding;
+            h->reserved = 0;
+
+            cl->next = tl;
+            cl = tl;
+
+            tl = ngx_chain_get_free_buf(r->pool, &f->free);
+            if (tl == NULL) {
+                return NGX_ERROR;
+            }
+
+            b = tl->buf;
+
+            b->tag = (ngx_buf_tag_t) &ngx_http_fastcgi_body_output_filter;
+            b->temporary = 1;
+
+            if (b->start == NULL) {
+                /* reserve space for maximum possible padding, 7 bytes */
+
+                b->start = ngx_palloc(r->pool,
+                                      sizeof(ngx_http_fastcgi_header_t) + 7);
+                if (b->start == NULL) {
+                    return NGX_ERROR;
+                }
+
+                b->pos = b->start;
+                b->last = b->start;
+
+                b->end = b->start + sizeof(ngx_http_fastcgi_header_t) + 7;
+            }
+
+            if (padding) {
+                ngx_memzero(b->last, padding);
+                b->last += padding;
+            }
+
+            cl->next = tl;
+            cl = tl;
+
+        } while (!next);
+
+        in = in->next;
+    }
+
+    if (last) {
+        h = (ngx_http_fastcgi_header_t *) cl->buf->last;
+        cl->buf->last += sizeof(ngx_http_fastcgi_header_t);
+
+        h->version = 1;
+        h->type = NGX_HTTP_FASTCGI_STDIN;
+        h->request_id_hi = 0;
+        h->request_id_lo = 1;
+        h->content_length_hi = 0;
+        h->content_length_lo = 0;
+        h->padding_length = 0;
+        h->reserved = 0;
+
+        cl->buf->last_buf = 1;
+
+    } else if (padding == 0) {
+        /* TODO: do not allocate buffers instead */
+        cl->buf->temporary = 0;
+        cl->buf->sync = 1;
+    }
+
+    cl->next = NULL;
+
+out:
+
+#if (NGX_DEBUG)
+
+    for (cl = out; cl; cl = cl->next) {
+        ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0,
+                       "fastcgi output out l:%d f:%d %p, pos %p, size: %z "
+                       "file: %O, size: %O",
+                       cl->buf->last_buf,
+                       cl->buf->in_file,
+                       cl->buf->start, cl->buf->pos,
+                       cl->buf->last - cl->buf->pos,
+                       cl->buf->file_pos,
+                       cl->buf->file_last - cl->buf->file_pos);
+    }
+
+#endif
+
+    rc = ngx_chain_writer(&r->upstream->writer, out);
+
+    ngx_chain_update_chains(r->pool, &f->free, &f->busy, &out,
+                         (ngx_buf_tag_t) &ngx_http_fastcgi_body_output_filter);
+
+    for (cl = f->free; cl; cl = cl->next) {
+
+        /* mark original buffers as sent */
+
+        if (cl->buf->shadow) {
+            if (cl->buf->last_shadow) {
+                b = cl->buf->shadow;
+                b->pos = b->last;
+            }
+
+            cl->buf->shadow = NULL;
+        }
+    }
+
+    return rc;
+}
+
+
+static ngx_int_t
 ngx_http_fastcgi_process_header(ngx_http_request_t *r)
 {
     u_char                         *p, *msg, *start, *last,
@@ -2405,6 +2725,7 @@ ngx_http_fastcgi_create_loc_conf(ngx_con
     conf->upstream.store_access = NGX_CONF_UNSET_UINT;
     conf->upstream.next_upstream_tries = NGX_CONF_UNSET_UINT;
     conf->upstream.buffering = NGX_CONF_UNSET;
+    conf->upstream.request_buffering = NGX_CONF_UNSET;
     conf->upstream.ignore_client_abort = NGX_CONF_UNSET;
     conf->upstream.force_ranges = NGX_CONF_UNSET;
 
@@ -2498,6 +2819,9 @@ ngx_http_fastcgi_merge_loc_conf(ngx_conf
     ngx_conf_merge_value(conf->upstream.buffering,
                               prev->upstream.buffering, 1);
 
+    ngx_conf_merge_value(conf->upstream.request_buffering,
+                              prev->upstream.request_buffering, 1);
+
     ngx_conf_merge_value(conf->upstream.ignore_client_abort,
                               prev->upstream.ignore_client_abort, 0);