changeset 5514:b7ee1bae0ffa

SPDY: implemented buffers chain splitting. It fixes "chain too big in spdy filter" alerts, and adds full support for rate limiting of SPDY streams.
author Valentin Bartenev <vbart@nginx.com>
date Tue, 14 Jan 2014 16:24:45 +0400
parents 311803b21504
children e5fb14e85040
files src/http/ngx_http_spdy.h src/http/ngx_http_spdy_filter_module.c
diffstat 2 files changed, 165 insertions(+), 29 deletions(-) [+]
line wrap: on
line diff
--- a/src/http/ngx_http_spdy.h
+++ b/src/http/ngx_http_spdy.h
@@ -123,6 +123,7 @@ struct ngx_http_spdy_stream_s {
 
     ngx_http_spdy_out_frame_t       *free_frames;
     ngx_chain_t                     *free_data_headers;
+    ngx_chain_t                     *free_bufs;
 
     unsigned                         priority:2;
     unsigned                         handled:1;
--- a/src/http/ngx_http_spdy_filter_module.c
+++ b/src/http/ngx_http_spdy_filter_module.c
@@ -34,6 +34,9 @@ static ngx_chain_t *ngx_http_spdy_send_c
 static ngx_inline ngx_int_t ngx_http_spdy_filter_send(
     ngx_connection_t *fc, ngx_http_spdy_stream_t *stream);
 
+static ngx_chain_t *ngx_http_spdy_filter_get_shadow(
+    ngx_http_spdy_stream_t *stream, ngx_buf_t *buf, size_t offset,
+    size_t size);
 static ngx_http_spdy_out_frame_t *ngx_http_spdy_filter_get_data_frame(
     ngx_http_spdy_stream_t *stream, size_t len, ngx_chain_t *first,
     ngx_chain_t *last);
@@ -618,8 +621,8 @@ ngx_http_spdy_header_filter(ngx_http_req
 static ngx_chain_t *
 ngx_http_spdy_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit)
 {
-    off_t                       size;
-    ngx_buf_t                  *b;
+    off_t                       size, offset;
+    size_t                      rest, frame_size;
     ngx_chain_t                *cl, *out, **ln;
     ngx_http_request_t         *r;
     ngx_http_spdy_stream_t     *stream;
@@ -639,48 +642,161 @@ ngx_http_spdy_send_chain(ngx_connection_
         return NULL;
     }
 
-    size = 0;
-    ln = &out;
+    size = ngx_buf_size(in->buf);
 
-    do {
-        b = in->buf;
-
+    if (in->buf->tag == (ngx_buf_tag_t) &ngx_http_spdy_filter_get_shadow) {
         cl = ngx_alloc_chain_link(r->pool);
         if (cl == NULL) {
             return NGX_CHAIN_ERROR;
         }
 
-        size += ngx_buf_size(b);
-        cl->buf = b;
-
-        *ln = cl;
-        ln = &cl->next;
+        cl->buf = in->buf;
+        in->buf = cl->buf->shadow;
 
-        in = in->next;
-
-    } while (in);
+        offset = ngx_buf_in_memory(in->buf)
+                 ? (cl->buf->pos - in->buf->pos)
+                 : (cl->buf->file_pos - in->buf->file_pos);
 
-    if (size > NGX_SPDY_MAX_FRAME_SIZE) {
-        ngx_log_error(NGX_LOG_ALERT, fc->log, 0,
-                      "FIXME: chain too big in spdy filter: %O", size);
-        return NGX_CHAIN_ERROR;
+        cl->next = stream->free_bufs;
+        stream->free_bufs = cl;
+
+    } else {
+        offset = 0;
     }
 
-    frame = ngx_http_spdy_filter_get_data_frame(stream, (size_t) size,
-                                                out, cl);
-    if (frame == NULL) {
-        return NGX_CHAIN_ERROR;
+    frame_size = (limit && limit < NGX_SPDY_MAX_FRAME_SIZE)
+                 ? limit : NGX_SPDY_MAX_FRAME_SIZE;
+
+    for ( ;; ) {
+        ln = &out;
+        rest = frame_size;
+
+        while ((off_t) rest >= size) {
+
+            if (offset) {
+                cl = ngx_http_spdy_filter_get_shadow(stream, in->buf,
+                                                     offset, size);
+                if (cl == NULL) {
+                    return NGX_CHAIN_ERROR;
+                }
+
+                offset = 0;
+
+            } else {
+                cl = ngx_alloc_chain_link(r->pool);
+                if (cl == NULL) {
+                    return NGX_CHAIN_ERROR;
+                }
+
+                cl->buf = in->buf;
+            }
+
+            *ln = cl;
+            ln = &cl->next;
+
+            rest -= size;
+            in = in->next;
+
+            if (in == NULL) {
+                frame_size -= rest;
+                rest = 0;
+                break;
+            }
+
+            size = ngx_buf_size(in->buf);
+        }
+
+        if (rest) {
+            cl = ngx_http_spdy_filter_get_shadow(stream, in->buf,
+                                                 offset, rest);
+            if (cl == NULL) {
+                return NGX_CHAIN_ERROR;
+            }
+
+            cl->buf->flush = 0;
+            cl->buf->last_buf = 0;
+
+            *ln = cl;
+
+            offset += rest;
+            size -= rest;
+        }
+
+        frame = ngx_http_spdy_filter_get_data_frame(stream, frame_size,
+                                                    out, cl);
+        if (frame == NULL) {
+            return NGX_CHAIN_ERROR;
+        }
+
+        ngx_http_spdy_queue_frame(stream->connection, frame);
+
+        stream->queued++;
+
+        if (in == NULL) {
+            break;
+        }
+
+        if (limit) {
+            limit -= frame_size;
+
+            if (limit == 0) {
+                break;
+            }
+
+            if (limit < NGX_SPDY_MAX_FRAME_SIZE) {
+                frame_size = limit;
+            }
+        }
     }
 
-    ngx_http_spdy_queue_frame(stream->connection, frame);
+    if (offset) {
+        cl = ngx_http_spdy_filter_get_shadow(stream, in->buf, offset, size);
+        if (cl == NULL) {
+            return NGX_CHAIN_ERROR;
+        }
 
-    stream->queued++;
+        in->buf = cl->buf;
+        ngx_free_chain(r->pool, cl);
+    }
 
     if (ngx_http_spdy_filter_send(fc, stream) == NGX_ERROR) {
         return NGX_CHAIN_ERROR;
     }
 
-    return NULL;
+    return in;
+}
+
+
+static ngx_chain_t *
+ngx_http_spdy_filter_get_shadow(ngx_http_spdy_stream_t *stream, ngx_buf_t *buf,
+    size_t offset, size_t size)
+{
+    ngx_buf_t    *chunk;
+    ngx_chain_t  *cl;
+
+    cl = ngx_chain_get_free_buf(stream->request->pool, &stream->free_bufs);
+    if (cl == NULL) {
+        return NULL;
+    }
+
+    chunk = cl->buf;
+
+    ngx_memcpy(chunk, buf, sizeof(ngx_buf_t));
+
+    chunk->tag = (ngx_buf_tag_t) &ngx_http_spdy_filter_get_shadow;
+    chunk->shadow = buf;
+
+    if (ngx_buf_in_memory(chunk)) {
+        chunk->pos += offset;
+        chunk->last = chunk->pos + size;
+    }
+
+    if (chunk->in_file) {
+        chunk->file_pos += offset;
+        chunk->file_last = chunk->file_pos + size;
+    }
+
+    return cl;
 }
 
 
@@ -747,7 +863,7 @@ ngx_http_spdy_filter_get_data_frame(ngx_
             buf->last = p;
             buf->end = p;
 
-            buf->tag = (ngx_buf_tag_t) &ngx_http_spdy_filter_module;
+            buf->tag = (ngx_buf_tag_t) &ngx_http_spdy_filter_get_data_frame;
             buf->memory = 1;
         }
 
@@ -825,6 +941,7 @@ static ngx_int_t
 ngx_http_spdy_data_frame_handler(ngx_http_spdy_connection_t *sc,
     ngx_http_spdy_out_frame_t *frame)
 {
+    ngx_buf_t               *buf;
     ngx_chain_t             *cl, *ln;
     ngx_http_spdy_stream_t  *stream;
 
@@ -832,7 +949,7 @@ ngx_http_spdy_data_frame_handler(ngx_htt
 
     cl = frame->first;
 
-    if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_spdy_filter_module) {
+    if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_spdy_filter_get_data_frame) {
 
         if (cl->buf->pos != cl->buf->last) {
             ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,
@@ -855,6 +972,18 @@ ngx_http_spdy_data_frame_handler(ngx_htt
     }
 
     for ( ;; ) {
+        if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_spdy_filter_get_shadow) {
+            buf = cl->buf->shadow;
+
+            if (ngx_buf_in_memory(buf)) {
+                buf->pos = cl->buf->pos;
+            }
+
+            if (buf->in_file) {
+                buf->file_pos = cl->buf->file_pos;
+            }
+        }
+
         if (ngx_buf_size(cl->buf) != 0) {
 
             if (cl != frame->first) {
@@ -871,7 +1000,13 @@ ngx_http_spdy_data_frame_handler(ngx_htt
 
         ln = cl->next;
 
-        ngx_free_chain(stream->request->pool, cl);
+        if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_spdy_filter_get_shadow) {
+            cl->next = stream->free_bufs;
+            stream->free_bufs = cl;
+
+        } else {
+            ngx_free_chain(stream->request->pool, cl);
+        }
 
         if (cl == frame->last) {
             goto done;