[PATCH] Allow Partial Content responses to satisfy Range requests

Steven Hartland steven.hartland at multiplay.co.uk
Mon Sep 1 22:57:05 UTC 2014


# HG changeset patch
# User Steven Hartland <steven.hartland at multiplay.co.uk>
# Date 1409611936 0
#      Mon Sep 01 22:52:16 2014 +0000
# Node ID 0dc608b347e24b914ee193214857de15aad2ac0b
# Parent  3f5f0ab59b359064db16e1aa52dfca335720dff6
Allow Partial Content responses to satisfy Range requests.

Previously on 200 responsed could statisfy Range requests, this adds support
for 206 responses (Partial Content) to also satisfy Range requests as long
as the Range fits within the Partial Content.

This can be used directly as well as by other modules where is can be used
to build more complex responses, such as the response to a Range request
from a predicable set of sub Range requests.

diff -r 3f5f0ab59b35 -r 0dc608b347e2 src/http/modules/ngx_http_range_filter_module.c
--- a/src/http/modules/ngx_http_range_filter_module.c	Mon Sep 01 18:20:18 2014 +0400
+++ b/src/http/modules/ngx_http_range_filter_module.c	Mon Sep 01 22:52:16 2014 +0000
@@ -54,6 +54,8 @@
 
 typedef struct {
     off_t        offset;
+    off_t        content_end;
+    off_t        content_length;
     ngx_str_t    boundary_header;
     ngx_array_t  ranges;
 } ngx_http_range_filter_ctx_t;
@@ -65,7 +67,8 @@
     ngx_http_range_filter_ctx_t *ctx);
 static ngx_int_t ngx_http_range_multipart_header(ngx_http_request_t *r,
     ngx_http_range_filter_ctx_t *ctx);
-static ngx_int_t ngx_http_range_not_satisfiable(ngx_http_request_t *r);
+static ngx_int_t ngx_http_range_not_satisfiable(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx);
 static ngx_int_t ngx_http_range_test_overlapped(ngx_http_request_t *r,
     ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in);
 static ngx_int_t ngx_http_range_singlepart_body(ngx_http_request_t *r,
@@ -76,6 +79,9 @@
 static ngx_int_t ngx_http_range_header_filter_init(ngx_conf_t *cf);
 static ngx_int_t ngx_http_range_body_filter_init(ngx_conf_t *cf);
 
+static ngx_int_t ngx_http_content_range_parse(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx);
+
 
 static ngx_http_module_t  ngx_http_range_header_filter_module_ctx = {
     NULL,                                  /* preconfiguration */
@@ -153,8 +159,8 @@
     ngx_http_range_filter_ctx_t  *ctx;
 
     if (r->http_version < NGX_HTTP_VERSION_10
-        || r->headers_out.status != NGX_HTTP_OK
-        || r != r->main
+        || (r->headers_out.status != NGX_HTTP_OK
+        && r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT)
         || r->headers_out.content_length_n == -1
         || !r->allow_ranges)
     {
@@ -230,26 +236,35 @@
 
     ranges = r->single_range ? 1 : clcf->max_ranges;
 
-    switch (ngx_http_range_parse(r, ctx, ranges)) {
+    switch (ngx_http_content_range_parse(r, ctx)) {
 
     case NGX_OK:
-        ngx_http_set_ctx(r, ctx, ngx_http_range_body_filter_module);
+        switch (ngx_http_range_parse(r, ctx, ranges)) {
 
-        r->headers_out.status = NGX_HTTP_PARTIAL_CONTENT;
-        r->headers_out.status_line.len = 0;
+        case NGX_OK:
+            ngx_http_set_ctx(r, ctx, ngx_http_range_body_filter_module);
 
-        if (ctx->ranges.nelts == 1) {
-            return ngx_http_range_singlepart_header(r, ctx);
+            r->headers_out.status = NGX_HTTP_PARTIAL_CONTENT;
+            r->headers_out.status_line.len = 0;
+
+            if (ctx->ranges.nelts == 1) {
+                return ngx_http_range_singlepart_header(r, ctx);
+            }
+
+            return ngx_http_range_multipart_header(r, ctx);
+
+        case NGX_HTTP_RANGE_NOT_SATISFIABLE:
+            return ngx_http_range_not_satisfiable(r, ctx);
+
+        case NGX_ERROR:
+            return NGX_ERROR;
+
+        default: /* NGX_DECLINED */
+            break;
         }
-
-        return ngx_http_range_multipart_header(r, ctx);
-
+        break;
     case NGX_HTTP_RANGE_NOT_SATISFIABLE:
-        return ngx_http_range_not_satisfiable(r);
-
-    case NGX_ERROR:
-        return NGX_ERROR;
-
+        return ngx_http_range_not_satisfiable(r, ctx);
     default: /* NGX_DECLINED */
         break;
     }
@@ -274,13 +289,12 @@
     ngx_uint_t ranges)
 {
     u_char            *p;
-    off_t              start, end, size, content_length;
+    off_t              start, end, size;
     ngx_uint_t         suffix;
     ngx_http_range_t  *range;
 
     p = r->headers_in.range->value.data + 6;
     size = 0;
-    content_length = r->headers_out.content_length_n;
 
     for ( ;; ) {
         start = 0;
@@ -298,6 +312,10 @@
                 start = start * 10 + *p++ - '0';
             }
 
+            if (start < ctx->offset) {
+                return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+            }
+
             while (*p == ' ') { p++; }
 
             if (*p++ != '-') {
@@ -307,7 +325,7 @@
             while (*p == ' ') { p++; }
 
             if (*p == ',' || *p == '\0') {
-                end = content_length;
+                end = ctx->content_length;
                 goto found;
             }
 
@@ -324,6 +342,10 @@
             end = end * 10 + *p++ - '0';
         }
 
+        if (end > ctx->content_end) {
+            return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+        }
+
         while (*p == ' ') { p++; }
 
         if (*p != ',' && *p != '\0') {
@@ -331,12 +353,12 @@
         }
 
         if (suffix) {
-            start = content_length - end;
-            end = content_length - 1;
+            start = ctx->content_length - end;
+            end = ctx->content_length - 1;
         }
 
-        if (end >= content_length) {
-            end = content_length;
+        if (end >= ctx->content_length) {
+            end = ctx->content_length;
 
         } else {
             end++;
@@ -369,7 +391,7 @@
         return NGX_HTTP_RANGE_NOT_SATISFIABLE;
     }
 
-    if (size > content_length) {
+    if (size > ctx->content_length) {
         return NGX_DECLINED;
     }
 
@@ -384,16 +406,18 @@
     ngx_table_elt_t   *content_range;
     ngx_http_range_t  *range;
 
-    content_range = ngx_list_push(&r->headers_out.headers);
-    if (content_range == NULL) {
-        return NGX_ERROR;
+    if (r->headers_out.content_range == NULL) {
+        content_range = ngx_list_push(&r->headers_out.headers);
+        if (content_range == NULL) {
+            return NGX_ERROR;
+        }
+        r->headers_out.content_range = content_range;
+        content_range->hash = 1;
+        ngx_str_set(&content_range->key, "Content-Range");
+    } else {
+        content_range = r->headers_out.content_range;
     }
 
-    r->headers_out.content_range = content_range;
-
-    content_range->hash = 1;
-    ngx_str_set(&content_range->key, "Content-Range");
-
     content_range->value.data = ngx_pnalloc(r->pool,
                                     sizeof("bytes -/") - 1 + 3 * NGX_OFF_T_LEN);
     if (content_range->value.data == NULL) {
@@ -407,7 +431,7 @@
     content_range->value.len = ngx_sprintf(content_range->value.data,
                                            "bytes %O-%O/%O",
                                            range->start, range->end - 1,
-                                           r->headers_out.content_length_n)
+                                           ctx->content_length)
                                - content_range->value.data;
 
     r->headers_out.content_length_n = range->end - range->start;
@@ -546,22 +570,25 @@
 
 
 static ngx_int_t
-ngx_http_range_not_satisfiable(ngx_http_request_t *r)
+ngx_http_range_not_satisfiable(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx)
 {
     ngx_table_elt_t  *content_range;
 
     r->headers_out.status = NGX_HTTP_RANGE_NOT_SATISFIABLE;
 
-    content_range = ngx_list_push(&r->headers_out.headers);
-    if (content_range == NULL) {
-        return NGX_ERROR;
+    if (r->headers_out.content_range == NULL) {
+        content_range = ngx_list_push(&r->headers_out.headers);
+        if (content_range == NULL) {
+            return NGX_ERROR;
+        }
+        r->headers_out.content_range = content_range;
+        content_range->hash = 1;
+        ngx_str_set(&content_range->key, "Content-Range");
+    } else {
+        content_range = r->headers_out.content_range;
     }
 
-    r->headers_out.content_range = content_range;
-
-    content_range->hash = 1;
-    ngx_str_set(&content_range->key, "Content-Range");
-
     content_range->value.data = ngx_pnalloc(r->pool,
                                        sizeof("bytes */") - 1 + NGX_OFF_T_LEN);
     if (content_range->value.data == NULL) {
@@ -570,7 +597,7 @@
 
     content_range->value.len = ngx_sprintf(content_range->value.data,
                                            "bytes */%O",
-                                           r->headers_out.content_length_n)
+                                           ctx->content_length)
                                - content_range->value.data;
 
     ngx_http_clear_content_length(r);
@@ -888,3 +915,77 @@
 
     return NGX_OK;
 }
+
+static ngx_int_t
+ngx_http_content_range_parse(ngx_http_request_t *r,
+    ngx_http_range_filter_ctx_t *ctx)
+{
+    u_char     *p;
+    off_t      start, end, len;
+
+    ctx->offset = 0;
+    ctx->content_end = r->headers_out.content_length_n - 1;
+    ctx->content_length = r->headers_out.content_length_n;
+
+    if (r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT) {
+       return NGX_OK;
+    }
+
+    if (r->headers_out.content_range == NULL
+       || r->headers_out.content_range->value.len == 0) {
+       return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+    }
+
+    if (r->headers_out.content_range->value.len < 7
+        || ngx_strncasecmp(r->headers_out.content_range->value.data,
+                           (u_char *) "bytes ", 6) != 0) {
+       return NGX_DECLINED;
+    }
+
+    start = 0;
+    end = 0;
+    len = 0;
+
+    p = r->headers_out.content_range->value.data + 6;
+
+    while (*p == ' ') { p++; }
+
+    if (*p < '0' || *p > '9') {
+        return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+    }
+
+    while (*p >= '0' && *p <= '9') {
+        start = start * 10 + *p++ - '0';
+    }
+
+    if (*p++ != '-') {
+        return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+    }
+
+    while (*p >= '0' && *p <= '9') {
+        end = end * 10 + *p++ - '0';
+    }
+
+    if (*p++ != '/') {
+        return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+    }
+
+    if (*p < '0' || *p > '9') {
+        return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+    }
+
+    while (*p >= '0' && *p <= '9') {
+        len = len * 10 + *p++ - '0';
+    }
+
+    if (*p != '\0') {
+        return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+    }
+
+    ctx->offset = start;
+    ctx->content_end = end;
+    ctx->content_length = len;
+
+    return NGX_OK;
+}
+
diff -r 3f5f0ab59b35 -r 0dc608b347e2 src/http/ngx_http_upstream.c
--- a/src/http/ngx_http_upstream.c	Mon Sep 01 18:20:18 2014 +0400
+++ b/src/http/ngx_http_upstream.c	Mon Sep 01 22:52:16 2014 +0000
@@ -286,6 +286,11 @@
                  ngx_http_upstream_copy_content_encoding, 0, 0 },
 #endif
 
+    { ngx_string("Content-Range"),
+                 ngx_http_upstream_ignore_header_line, 0,
+                 ngx_http_upstream_copy_allow_ranges,
+                 offsetof(ngx_http_headers_out_t, content_range), 1 },
+
     { ngx_null_string, NULL, 0, NULL, 0, 0 }
 };
 
@@ -4362,33 +4367,23 @@
 ngx_http_upstream_copy_allow_ranges(ngx_http_request_t *r,
     ngx_table_elt_t *h, ngx_uint_t offset)
 {
-    ngx_table_elt_t  *ho;
-
 #if (NGX_HTTP_CACHE)
-
     if (r->cached) {
         r->allow_ranges = 1;
-        return NGX_OK;
+        if (offsetof(ngx_http_headers_out_t, accept_ranges) == offset) {
+            return NGX_OK;
+        }
     }
 
     if (r->upstream->cacheable) {
         r->allow_ranges = 1;
         r->single_range = 1;
-        return NGX_OK;
-    }
-
+        if (offsetof(ngx_http_headers_out_t, accept_ranges) == offset) {
+            return NGX_OK;
+        }
+    }
 #endif
-
-    ho = ngx_list_push(&r->headers_out.headers);
-    if (ho == NULL) {
-        return NGX_ERROR;
-    }
-
-    *ho = *h;
-
-    r->headers_out.accept_ranges = ho;
-
-    return NGX_OK;
+    return ngx_http_upstream_copy_header_line(r, h, offset);
 }
 
 



More information about the nginx-devel mailing list