Slice module

Roman Arutyunyan arut at nginx.com
Tue Sep 29 16:00:25 UTC 2015


Hello,

I'm happy to publish the experimental Slice module.  The module makes it
possible to split a big upstream response into smaller parts and cache them
independently.

The module supports range requests.  When a part of a file is requested,
only the required slice upstream requests are made.  If caching is enabled,
future requests will only go to upstream for missing slices.

The module adds

- "slice" directive setting the slice size.

- "$slice_range" variable, which must be added to the cache key
  expression and passed to upstream as the Range header value.
  The variable holds current slice range in the HTTP Range field format.


Build
-----

Use the --with-http_slice_module configure script option.


Example
-------

location / {
    slice 1m;

    proxy_cache cache;
    proxy_cache_key $uri$is_args$args$slice_range;
    proxy_set_header Range $slice_range;
    proxy_cache_valid 200 206 1h;
    proxy_pass http://127.0.0.1:9000;
}


Known issues
------------

The module can lead to excessive memory and file handle usage.


Thanks for testing.

--
Best wishes,
Roman Arutyunyan
-------------- next part --------------
diff -r 0f313cf0a1ee auto/modules
--- a/auto/modules	Tue Sep 22 17:36:22 2015 +0300
+++ b/auto/modules	Mon Sep 28 17:07:15 2015 +0300
@@ -73,6 +73,11 @@ if [ $HTTP_SSI = YES ]; then
 fi
 
 
+if [ $HTTP_SLICE = YES ]; then
+    HTTP_POSTPONE=YES
+fi
+
+
 if [ $HTTP_ADDITION = YES ]; then
     HTTP_POSTPONE=YES
 fi
@@ -140,6 +145,11 @@ if [ $HTTP_SSI = YES ]; then
     HTTP_SRCS="$HTTP_SRCS $HTTP_SSI_SRCS"
 fi
 
+if [ $HTTP_SLICE = YES ]; then
+    HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES $HTTP_SLICE_FILTER_MODULE"
+    HTTP_SRCS="$HTTP_SRCS $HTTP_SLICE_SRCS"
+fi
+
 if [ $HTTP_CHARSET = YES ]; then
     HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES $HTTP_CHARSET_FILTER_MODULE"
     HTTP_SRCS="$HTTP_SRCS $HTTP_CHARSET_SRCS"
diff -r 0f313cf0a1ee auto/options
--- a/auto/options	Tue Sep 22 17:36:22 2015 +0300
+++ b/auto/options	Mon Sep 28 17:07:15 2015 +0300
@@ -60,6 +60,7 @@ HTTP_GZIP=YES
 HTTP_SSL=NO
 HTTP_V2=NO
 HTTP_SSI=YES
+HTTP_SLICE=NO
 HTTP_POSTPONE=NO
 HTTP_REALIP=NO
 HTTP_XSLT=NO
@@ -226,6 +227,7 @@ do
         --with-http_random_index_module) HTTP_RANDOM_INDEX=YES      ;;
         --with-http_secure_link_module)  HTTP_SECURE_LINK=YES       ;;
         --with-http_degradation_module)  HTTP_DEGRADATION=YES       ;;
+        --with-http_slice_module)        HTTP_SLICE=YES             ;;
 
         --without-http_charset_module)   HTTP_CHARSET=NO            ;;
         --without-http_gzip_module)      HTTP_GZIP=NO               ;;
@@ -395,6 +397,7 @@ cat << END
   --with-http_secure_link_module     enable ngx_http_secure_link_module
   --with-http_degradation_module     enable ngx_http_degradation_module
   --with-http_stub_status_module     enable ngx_http_stub_status_module
+  --with-http_slice_module           enable ngx_http_slice_module
 
   --without-http_charset_module      disable ngx_http_charset_module
   --without-http_gzip_module         disable ngx_http_gzip_module
diff -r 0f313cf0a1ee auto/sources
--- a/auto/sources	Tue Sep 22 17:36:22 2015 +0300
+++ b/auto/sources	Mon Sep 28 17:07:15 2015 +0300
@@ -347,6 +347,10 @@ HTTP_SSI_DEPS=src/http/modules/ngx_http_
 HTTP_SSI_SRCS=src/http/modules/ngx_http_ssi_filter_module.c
 
 
+HTTP_SLICE_FILTER_MODULE=ngx_http_slice_filter_module
+HTTP_SLICE_SRCS=src/http/modules/ngx_http_slice_filter_module.c
+
+
 HTTP_XSLT_FILTER_MODULE=ngx_http_xslt_filter_module
 HTTP_XSLT_SRCS=src/http/modules/ngx_http_xslt_filter_module.c
 
diff -r 0f313cf0a1ee src/http/modules/ngx_http_slice_filter_module.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/http/modules/ngx_http_slice_filter_module.c	Mon Sep 28 17:07:15 2015 +0300
@@ -0,0 +1,777 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    size_t     size;
+} ngx_http_slice_loc_conf_t;
+
+
+typedef struct {
+    off_t      start;
+    off_t      end;
+    off_t      content_length;
+    off_t      offset;
+    ngx_str_t  range;
+    ngx_str_t  etag;
+    unsigned   last:1;
+    unsigned   bad_range:1;
+} ngx_http_slice_ctx_t;
+
+
+static ngx_int_t ngx_http_slice_handler(ngx_http_request_t *r);
+static ngx_int_t ngx_http_slice_parse_request_range(ngx_http_request_t *r,
+    ngx_http_slice_ctx_t *ctx);
+static ngx_int_t ngx_http_slice_header_filter(ngx_http_request_t *r);
+static ngx_int_t ngx_http_slice_parse_response_range(ngx_http_request_t *r,
+    ngx_http_slice_ctx_t *ctx);
+static ngx_int_t ngx_http_slice_body_filter(ngx_http_request_t *r,
+    ngx_chain_t *in);
+static ngx_int_t ngx_http_slice_range_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static void *ngx_http_slice_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_slice_merge_loc_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+static ngx_int_t ngx_http_slice_add_variables(ngx_conf_t *cf);
+static ngx_int_t ngx_http_slice_init(ngx_conf_t *cf);
+
+
+static ngx_command_t  ngx_http_slice_filter_commands[] = {
+
+    { ngx_string("slice"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_slice_loc_conf_t, size),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_slice_filter_module_ctx = {
+    ngx_http_slice_add_variables,          /* preconfiguration */
+    ngx_http_slice_init,                   /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_slice_create_loc_conf,        /* create location configuration */
+    ngx_http_slice_merge_loc_conf          /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_slice_filter_module = {
+    NGX_MODULE_V1,
+    &ngx_http_slice_filter_module_ctx,     /* module context */
+    ngx_http_slice_filter_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_str_t  ngx_http_slice_range_name = ngx_string("slice_range");
+
+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_slice_handler(ngx_http_request_t *r)
+{
+    off_t                       start, end;
+    u_char                     *p;
+    ngx_http_slice_ctx_t       *ctx;
+    ngx_http_slice_loc_conf_t  *slcf;
+
+    slcf = ngx_http_get_module_loc_conf(r, ngx_http_slice_filter_module);
+    if (slcf->size == 0) {
+        return NGX_DECLINED;
+    }
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module);
+    if (ctx) {
+        return NGX_DECLINED;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http slice handler");
+
+    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_slice_ctx_t));
+    if (ctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_http_set_ctx(r, ctx, ngx_http_slice_filter_module);
+
+    if (ngx_http_slice_parse_request_range(r, ctx) != NGX_OK) {
+        ctx->bad_range = 1;
+    }
+
+    if (ctx->start > 0) {
+        start = slcf->size * (ctx->start / slcf->size);
+
+    } else {
+        start = 0;
+    }
+
+    end = start + slcf->size - 1;
+
+    p = ngx_pnalloc(r->pool, sizeof("bytes=-") - 1 + 2 * NGX_OFF_T_LEN);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    ctx->range.data = p;
+    ctx->range.len = ngx_sprintf(p, "bytes=%O-%O", start, end) - p;
+
+    return NGX_DECLINED;
+}
+
+
+static ngx_int_t
+ngx_http_slice_parse_request_range(ngx_http_request_t *r,
+    ngx_http_slice_ctx_t *ctx)
+{
+    off_t        start, end, cutoff, cutlim;
+    u_char      *p;
+    ngx_uint_t   suffix;
+
+    if (r->method == NGX_HTTP_HEAD) {
+        return NGX_OK;
+    }
+
+    if (r->headers_in.range == NULL
+        || r->headers_in.range->value.len < 7
+        || ngx_strncasecmp(r->headers_in.range->value.data,
+                           (u_char *) "bytes=", 6)
+           != 0)
+    {
+        ctx->end = -1;
+        return NGX_OK;
+    }
+
+    p = r->headers_in.range->value.data + 6;
+
+    cutoff = NGX_MAX_OFF_T_VALUE / 10;
+    cutlim = NGX_MAX_OFF_T_VALUE % 10;
+
+    start = 0;
+    end = 0;
+    suffix = 0;
+
+    while (*p == ' ') { p++; }
+
+    if (*p != '-') {
+        if (*p < '0' || *p > '9') {
+            return NGX_ERROR;
+        }
+
+        while (*p >= '0' && *p <= '9') {
+            if (start >= cutoff && (start > cutoff || *p - '0' > cutlim)) {
+                return NGX_ERROR;
+            }
+
+            start = start * 10 + *p++ - '0';
+        }
+
+        while (*p == ' ') { p++; }
+
+        if (*p++ != '-') {
+            return NGX_ERROR;
+        }
+
+        while (*p == ' ') { p++; }
+
+        if (*p == ',') {
+            ctx->end = -1;
+            return NGX_OK;
+        }
+
+        if (*p == '\0') {
+            ctx->start = start;
+            ctx->end = -1;
+            return NGX_OK;
+        }
+
+    } else {
+        suffix = 1;
+        p++;
+    }
+
+    if (*p < '0' || *p > '9') {
+        return NGX_ERROR;
+    }
+
+    while (*p >= '0' && *p <= '9') {
+        if (end >= cutoff && (end > cutoff || *p - '0' > cutlim)) {
+            return NGX_ERROR;
+        }
+
+        end = end * 10 + *p++ - '0';
+    }
+
+    while (*p == ' ') { p++; }
+
+    if (*p != ',' && *p != '\0') {
+        return NGX_ERROR;
+    }
+
+    if (suffix) {
+        ctx->start = -end;
+        ctx->end = -1;
+        return NGX_OK;
+    }
+
+    if (*p == ',') {
+        ctx->end = -1;
+        return NGX_OK;
+    }
+
+    ctx->start = start;
+    ctx->end = end + 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_slice_header_filter(ngx_http_request_t *r)
+{
+    ngx_table_elt_t       *h;
+    ngx_http_slice_ctx_t  *ctx;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module);
+    if (ctx == NULL) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http slice header filter");
+
+    if (r->headers_out.status != NGX_HTTP_OK
+        && r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT)
+    {
+        if (r != r->main) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "bad status code %ui in slice response",
+                          r->headers_out.status);
+            return NGX_ERROR;
+        }
+
+        return ngx_http_next_header_filter(r);
+    }
+
+    if (ngx_http_slice_parse_response_range(r, ctx) != NGX_OK) {
+        if (r != r->main) {
+            return NGX_ERROR;
+        }
+
+        return ngx_http_filter_finalize_request(r, NULL, NGX_HTTP_BAD_GATEWAY);
+    }
+
+    if (r != r->main) {
+        if (ctx->etag.len) {
+            h = r->headers_out.etag;
+
+            if (h == NULL
+                || h->value.len != ctx->etag.len
+                || ngx_strncmp(h->value.data, ctx->etag.data, ctx->etag.len)
+                   != 0)
+            {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "bad etag in slice response");
+                return NGX_ERROR;
+            }
+        }
+
+        return ngx_http_next_header_filter(r);
+    }
+
+    if (ctx->start < 0) {
+        ctx->start += ctx->content_length;
+        if (ctx->start < 0) {
+            ctx->start = 0;
+        }
+    }
+
+    if (ctx->end == -1 || ctx->end > ctx->content_length) {
+        ctx->end = ctx->content_length;
+    }
+
+    if (ctx->start >= ctx->end) {
+        ctx->bad_range = 1;
+    }
+
+    if (ctx->bad_range) {
+        h = ngx_list_push(&r->headers_out.headers);
+        if (h == NULL) {
+            return NGX_ERROR;
+        }
+
+        h->hash = 1;
+        ngx_str_set(&h->key, "Content-Range");
+
+        h->value.data = ngx_pnalloc(r->pool,
+                                    sizeof("bytes */") - 1 + NGX_OFF_T_LEN);
+        if (h->value.data == NULL) {
+            return NGX_ERROR;
+        }
+
+        h->value.len = ngx_sprintf(h->value.data, "bytes */%O",
+                                   ctx->content_length)
+                       - h->value.data;
+
+        ngx_http_clear_content_length(r);
+
+        r->headers_out.content_range = h;
+        r->headers_out.status = NGX_HTTP_RANGE_NOT_SATISFIABLE;
+
+        return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+    }
+
+    if (r->headers_in.range == NULL) {
+        ngx_http_clear_content_length(r);
+
+        r->headers_out.content_length_n = ctx->content_length;
+        r->headers_out.status = NGX_HTTP_OK;
+        r->headers_out.status_line.len = 0;
+
+    } else {
+        h = ngx_list_push(&r->headers_out.headers);
+        if (h == NULL) {
+            return NGX_ERROR;
+        }
+
+        h->hash = 1;
+        ngx_str_set(&h->key, "Content-Range");
+
+        h->value.data = ngx_pnalloc(r->pool,
+                                    sizeof("bytes -/") - 1 + 3 * NGX_OFF_T_LEN);
+        if (h->value.data == NULL) {
+            return NGX_ERROR;
+        }
+
+        h->value.len = ngx_sprintf(h->value.data, "bytes %O-%O/%O",
+                                   ctx->start, ctx->end - 1,
+                                   ctx->content_length)
+                       - h->value.data;
+
+        ngx_http_clear_content_length(r);
+
+        r->headers_out.content_range = h;
+        r->headers_out.content_length_n = ctx->end - ctx->start;
+        r->headers_out.status = NGX_HTTP_PARTIAL_CONTENT;
+        r->headers_out.status_line.len = 0;
+    }
+
+    return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_slice_parse_response_range(ngx_http_request_t *r,
+    ngx_http_slice_ctx_t *ctx)
+{
+    off_t             start, end, content_length, cutoff, cutlim;
+    u_char           *p;
+    ngx_table_elt_t  *h;
+
+    if (r->headers_out.status == NGX_HTTP_PARTIAL_CONTENT) {
+        h = r->headers_out.content_range;
+        if (h == NULL) {
+            return NGX_ERROR;
+        }
+
+        h->hash = 0;
+        r->headers_out.content_range = NULL;
+
+        cutoff = NGX_MAX_OFF_T_VALUE / 10;
+        cutlim = NGX_MAX_OFF_T_VALUE % 10;
+
+        start = 0;
+        end = 0;
+        content_length = 0;
+
+        if (h->value.len < 7
+            || ngx_strncmp(h->value.data, "bytes ", 6) != 0)
+        {
+            return NGX_ERROR;
+        }
+
+        p = h->value.data + 6;
+
+        while (*p == ' ') { p++; }
+
+        if (*p < '0' || *p > '9') {
+            return NGX_ERROR;
+        }
+
+        while (*p >= '0' && *p <= '9') {
+            if (start >= cutoff && (start > cutoff || *p - '0' > cutlim)) {
+                return NGX_ERROR;
+            }
+
+            start = start * 10 + *p++ - '0';
+        }
+
+        while (*p == ' ') { p++; }
+
+        if (*p++ != '-') {
+            return NGX_ERROR;
+        }
+
+        while (*p == ' ') { p++; }
+
+        if (*p < '0' || *p > '9') {
+            return NGX_ERROR;
+        }
+
+        while (*p >= '0' && *p <= '9') {
+            if (end >= cutoff && (end > cutoff || *p - '0' > cutlim)) {
+                return NGX_ERROR;
+            }
+
+            end = end * 10 + *p++ - '0';
+        }
+
+        end++;
+
+        while (*p == ' ') { p++; }
+
+        if (*p++ != '/') {
+            return NGX_ERROR;
+        }
+
+        while (*p == ' ') { p++; }
+
+        if (*p == '*') {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "no complete length in slice response");
+            return NGX_ERROR;
+        }
+
+        if (*p < '0' || *p > '9') {
+            return NGX_ERROR;
+        }
+
+        while (*p >= '0' && *p <= '9') {
+            if (content_length >= cutoff
+                && (content_length > cutoff || *p - '0' > cutlim))
+            {
+                return NGX_ERROR;
+            }
+
+            content_length = content_length * 10 + *p++ - '0';
+        }
+
+        while (*p == ' ') { p++; }
+
+        if (*p != '\0') {
+            return NGX_ERROR;
+        }
+
+    } else { /* r->headers_out.status == NGX_HTTP_OK */
+
+        content_length = r->headers_out.content_length_n;
+
+        if (content_length == -1) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "no content length in slice response");
+            return NGX_ERROR;
+        }
+
+        start = 0;
+        end = content_length;
+    }
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http slice range: %O-%O/%O",
+                   start, end, content_length);
+
+    /* make sure we received at least one byte from the range */
+
+    if (ctx->start >= 0
+        && !(ctx->start >= start && ctx->start < end))
+    {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "bad range in slice response: %O-%O", start, end);
+        return NGX_ERROR;
+    }
+
+    ctx->offset = start;
+    ctx->content_length = content_length;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_slice_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    off_t                       start, end;
+    u_char                     *p;
+    ngx_buf_t                  *b;
+    ngx_int_t                   rc;
+    ngx_chain_t                *out, *cl, **ll;
+    ngx_http_request_t         *sr;
+    ngx_http_slice_ctx_t       *ctx, *sctx, *pctx;
+    ngx_http_slice_loc_conf_t  *slcf;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module);
+    if (ctx == NULL) {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http slice body filter");
+
+    if (r->headers_out.status != NGX_HTTP_OK
+        && r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT)
+    {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    out = NULL;
+    ll = &out;
+
+    for (cl = in; cl; cl = cl->next) {
+
+        b = cl->buf;
+
+        start = ctx->offset;
+        end = ctx->offset + ngx_buf_size(b);
+
+        ctx->offset = end;
+
+        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http slice body buf: %O-%O, l:%d",
+                       start, end, (int) b->last_buf);
+
+        if (b->last_buf) {
+            b->last_buf = 0;
+            b->sync = 1;
+            ctx->last = 1;
+        }
+
+        if (ngx_buf_special(b)) {
+            *ll = cl;
+            ll = &cl->next;
+            continue;
+        }
+
+        if (ctx->end <= start || ctx->start >= end) {
+
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http slice body skip");
+
+            if (b->in_file) {
+                b->file_pos = b->file_last;
+            }
+
+            b->pos = b->last;
+            b->sync = 1;
+
+            continue;
+        }
+
+        if (ctx->start > start) {
+
+            if (b->in_file) {
+                b->file_pos += ctx->start - start;
+            }
+
+            if (ngx_buf_in_memory(b)) {
+                b->pos += (size_t) (ctx->start - start);
+            }
+        }
+
+        if (ctx->end <= end) {
+
+            if (b->in_file) {
+                b->file_last -= end - ctx->end;
+            }
+
+            if (ngx_buf_in_memory(b)) {
+                b->last -= (size_t) (end - ctx->end);
+            }
+
+            b->last_buf = 1;
+            *ll = cl;
+            cl->next = NULL;
+
+            break;
+        }
+
+        *ll = cl;
+        ll = &cl->next;
+    }
+
+    rc = ngx_http_next_body_filter(r, out);
+    if (rc == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    if (r != r->main && out) {
+        pctx = ngx_http_get_module_ctx(r->main, ngx_http_slice_filter_module);
+        if (pctx) {
+            pctx->offset = ctx->offset;
+        }
+    }
+
+    if (r != r->main
+        || !ctx->last
+        || ctx->offset >= ctx->end)
+    {
+        return rc;
+    }
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http slice next offset:%O, start:%O, end:%O",
+                   ctx->offset, ctx->start, ctx->end);
+
+    if (ngx_http_subrequest(r, &r->uri, &r->args, &sr, NULL, 0) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    sctx = ngx_pcalloc(r->pool, sizeof(ngx_http_slice_ctx_t));
+    if (sctx == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_http_set_ctx(sr, sctx, ngx_http_slice_filter_module);
+
+    sctx->start = ngx_max(ctx->start, ctx->offset);
+    sctx->end = ctx->end;
+
+    if (r->headers_out.etag) {
+        sctx->etag = r->headers_out.etag->value;
+    }
+
+    slcf = ngx_http_get_module_loc_conf(r, ngx_http_slice_filter_module);
+
+    start = slcf->size * (sctx->start / slcf->size);
+    end = start + slcf->size - 1;
+
+    p = ngx_pnalloc(r->pool, sizeof("bytes=-") - 1 + 2 * NGX_OFF_T_LEN);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    sctx->range.data = p;
+    sctx->range.len = ngx_sprintf(p, "bytes=%O-%O", start, end) - p;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http slice subrequest range: \"%V\"", &sctx->range);
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_http_slice_range_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_http_slice_ctx_t  *ctx;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module);
+    if (ctx == NULL) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    v->data= ctx->range.data;
+    v->valid = 1;
+    v->not_found = 0;
+    v->no_cacheable = 1;
+    v->len = ctx->range.len;
+
+    return NGX_OK;
+}
+
+
+static void *
+ngx_http_slice_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_slice_loc_conf_t  *slcf;
+
+    slcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_slice_loc_conf_t));
+    if (slcf == NULL) {
+        return NULL;
+    }
+
+    slcf->size = NGX_CONF_UNSET_SIZE;
+
+    return slcf;
+}
+
+
+static char *
+ngx_http_slice_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_slice_loc_conf_t *prev = parent;
+    ngx_http_slice_loc_conf_t *conf = child;
+
+    ngx_conf_merge_size_value(conf->size, prev->size, 0);
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_slice_add_variables(ngx_conf_t *cf)
+{
+    ngx_http_variable_t  *var;
+
+    var = ngx_http_add_variable(cf, &ngx_http_slice_range_name, 0);
+    if (var == NULL) {
+        return NGX_ERROR;
+    }
+
+    var->get_handler = ngx_http_slice_range_variable;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_slice_init(ngx_conf_t *cf)
+{
+    ngx_http_handler_pt        *h;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_slice_handler;
+
+    ngx_http_next_header_filter = ngx_http_top_header_filter;
+    ngx_http_top_header_filter = ngx_http_slice_header_filter;
+
+    ngx_http_next_body_filter = ngx_http_top_body_filter;
+    ngx_http_top_body_filter = ngx_http_slice_body_filter;
+
+    return NGX_OK;
+}
diff -r 0f313cf0a1ee src/http/ngx_http_upstream.c
--- a/src/http/ngx_http_upstream.c	Tue Sep 22 17:36:22 2015 +0300
+++ b/src/http/ngx_http_upstream.c	Mon Sep 28 17:07:15 2015 +0300
@@ -291,6 +291,11 @@ ngx_http_upstream_header_t  ngx_http_ups
                  ngx_http_upstream_process_transfer_encoding, 0,
                  ngx_http_upstream_ignore_header_line, 0, 0 },
 
+    { ngx_string("Content-Range"),
+                 ngx_http_upstream_ignore_header_line, 0,
+                 ngx_http_upstream_copy_header_line,
+                 offsetof(ngx_http_headers_out_t, content_range), 0 },
+
 #if (NGX_HTTP_GZIP)
     { ngx_string("Content-Encoding"),
                  ngx_http_upstream_process_header_line,


More information about the nginx-devel mailing list