[PATCH] Allow Partial Content responses to satisfy Range requests

Steven Hartland steven.hartland at multiplay.co.uk
Tue Nov 25 14:30:10 UTC 2014


Sent this one a while back but never had any feedback so updating to 
latest code base and resending.

Previously only 200 responses could satisfy 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 it can be 
used to build more complex responses, such as the response to a Range 
request from a predicable set of sub Range requests.

We have built a caching solution with one such custom module that allows 
effective caching of gaming content from all the major game distribution 
networks. More details on this can be found here:
http://blog.multiplay.co.uk/2014/04/lancache-dynamically-caching-game-installs-at-lans-using-nginx/

We'd like to release this module but it relies on having these 
enhancements to the nginx core, so would love to get them integrated.

     Regards
     Steve

On 25/11/2014 14:22, Steven Hartland wrote:
> # HG changeset patch
> # User Steven Hartland <steven.hartland at multiplay.co.uk>
> # Date 1416925134 0
> #      Tue Nov 25 14:18:54 2014 +0000
> # Node ID 0c3c06fabfc3b1c57710c0cced4837c10e3e9bbb
> # Parent  7d7eac6e31df1d962a644f8093c1fbb8f91620ce
> Allow Partial Content responses to satisfy Range requests.
>
> diff -r 7d7eac6e31df -r 0c3c06fabfc3 src/http/modules/ngx_http_range_filter_module.c
> --- a/src/http/modules/ngx_http_range_filter_module.c	Tue Nov 04 19:56:23 2014 +0900
> +++ b/src/http/modules/ngx_http_range_filter_module.c	Tue Nov 25 14:18:54 2014 +0000
> @@ -54,6 +54,7 @@
>   
>   typedef struct {
>       off_t        offset;
> +    off_t        content_length;
>       ngx_str_t    boundary_header;
>       ngx_array_t  ranges;
>   } ngx_http_range_filter_ctx_t;
> @@ -65,7 +66,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 +78,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 +158,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 +235,31 @@
>   
>       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:
> +        switch (ngx_http_range_parse(r, ctx, ranges)) {
> +        case NGX_OK:
> +            ngx_http_set_ctx(r, ctx, ngx_http_range_body_filter_module);
>   
> -    case NGX_OK:
> -        ngx_http_set_ctx(r, ctx, ngx_http_range_body_filter_module);
> +            r->headers_out.status = NGX_HTTP_PARTIAL_CONTENT;
> +            r->headers_out.status_line.len = 0;
>   
> -        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);
> +            }
>   
> -        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);
> +
> +        case NGX_ERROR:
> +            return NGX_ERROR;
> +
> +        default: /* NGX_DECLINED */
> +            break;
>           }
> -
> -        return ngx_http_range_multipart_header(r, ctx);
> -
> -    case NGX_HTTP_RANGE_NOT_SATISFIABLE:
> -        return ngx_http_range_not_satisfiable(r);
> -
> -    case NGX_ERROR:
> -        return NGX_ERROR;
> -
> +        break;
>       default: /* NGX_DECLINED */
>           break;
>       }
> @@ -274,13 +284,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 +307,10 @@
>                   start = start * 10 + *p++ - '0';
>               }
>   
> +            if (start < ctx->offset) {
> +                return NGX_HTTP_RANGE_NOT_SATISFIABLE;
> +            }
> +
>               while (*p == ' ') { p++; }
>   
>               if (*p++ != '-') {
> @@ -307,7 +320,7 @@
>               while (*p == ' ') { p++; }
>   
>               if (*p == ',' || *p == '\0') {
> -                end = content_length;
> +                end = ctx->content_length;
>                   goto found;
>               }
>   
> @@ -331,12 +344,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 +382,7 @@
>           return NGX_HTTP_RANGE_NOT_SATISFIABLE;
>       }
>   
> -    if (size > content_length) {
> +    if (size > ctx->content_length) {
>           return NGX_DECLINED;
>       }
>   
> @@ -384,16 +397,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 +422,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 +561,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 +588,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 +906,76 @@
>   
>       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_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_length = len;
> +
> +    return NGX_OK;
> +}
> +
> diff -r 7d7eac6e31df -r 0c3c06fabfc3 src/http/ngx_http_upstream.c
> --- a/src/http/ngx_http_upstream.c	Tue Nov 04 19:56:23 2014 +0900
> +++ b/src/http/ngx_http_upstream.c	Tue Nov 25 14:18:54 2014 +0000
> @@ -292,6 +292,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 }
>   };
>   
> @@ -4499,37 +4504,26 @@
>   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 (r->upstream->conf->force_ranges) {
>           return NGX_OK;
>       }
> -
>   #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);
>   }
>   
>   
>
> _______________________________________________
> nginx-devel mailing list
> nginx-devel at nginx.org
> http://mailman.nginx.org/mailman/listinfo/nginx-devel



More information about the nginx-devel mailing list