Welcome! Log In Create A New Profile

Advanced

Re: [PATCH] Allow Partial Content responses to satisfy Range requests

November 25, 2014 09:30AM
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@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@nginx.org
> http://mailman.nginx.org/mailman/listinfo/nginx-devel

_______________________________________________
nginx-devel mailing list
nginx-devel@nginx.org
http://mailman.nginx.org/mailman/listinfo/nginx-devel
Subject Author Views Posted

[PATCH] Allow Partial Content responses to satisfy Range requests

steveh 824 November 25, 2014 09:24AM

Re: [PATCH] Allow Partial Content responses to satisfy Range requests

steveh 678 November 25, 2014 09:30AM

Re: [PATCH] Allow Partial Content responses to satisfy Range requests

Maxim Dounin 367 December 04, 2014 02:40PM

Re: [PATCH] Allow Partial Content responses to satisfy Range requests

steveh 493 December 04, 2014 03:34PM



Sorry, you do not have permission to post/reply in this forum.

Online Users

Guests: 193
Record Number of Users: 8 on April 13, 2023
Record Number of Guests: 421 on December 02, 2018
Powered by nginx      Powered by FreeBSD      PHP Powered      Powered by MariaDB      ipv6 ready