[PATCH] Allow Partial Content responses to satisfy Range requests
Steven Hartland
steven.hartland at multiplay.co.uk
Fri Dec 5 19:38:18 UTC 2014
First off thanks for reviewing, comments / questions inline below
On 05/12/2014 16:00, Maxim Dounin wrote:
> Hello!
>
> On Thu, Dec 04, 2014 at 09:07:57PM +0000, Steven Hartland wrote:
>
>> # HG changeset patch
>> # User Steven Hartland <steven.hartland at multiplay.co.uk>
>> # Date 1417727204 0
>> # Thu Dec 04 21:06:44 2014 +0000
>> # Node ID 05d3973ece9af030d0312932938fc3d1f2f139dd
>> # Parent 1573fc7875fa09ee55763ce7ddc4e98d61e1deaf
>> Allow Partial Content responses to satisfy Range requests
> I would suggest something like:
>
> Range filter: support for 206 Partial Content responses.
>
> Some explanation on how to use these changes may be also helpful,
> as out of the box nginx won't be able to do anything good with it.
Looks like I lost the full comment when trying to make hg do a squash,
its not as nice to work with as git, which I guess is why Google are
migrating their golang repo to github ;-)
I'll restore this in the next patch but for now here's some details.
The driver for the changes is an additional module we've been developing
which based on this core enhancement can use predictably split range
requests to satisfy other requests.
This allows nginx to cache the responses to client downloads that use
random ranges to achieve full file downloads, such clients include EA's
Origin, Microsoft's XBox and Blizzard's downloaders which source from
CDN's such as Akamai and Limelight Networks.
What the module does is rewrite the random range request sent by the
client into one or more ranges with predictable boundaries. Then in the
uncached case it makes the required requests to the origin allowing
nginx core to cache the results. As the responses come in it combines
them to satisfy the initial range request sent by the client. Of course
some may be previously cached responses.
The result is a caching proxy that can build up a full copy of the file
by downloading individual ranges, none of which overlap; without which
you see massive duplication in the cache, resulting not only in wasted
local space but also slower downloads as the cache does multiple
requests for the same data from the origin server.
This could also be used to return a quicker response to the client when
caching a large file from an upstream source. Currently in a multi
client scenario where you want to avoid duplicate requests its requires
the entire file to be downloaded and cached before the responses start.
If the file where split into ranges the clients would only have to wait
until the current chunk processed until it could start getting data,
reducing the risk for timeouts with large files.
More information on this can be seen on our blog here:
http://blog.multiplay.co.uk/2014/04/lancache-dynamically-caching-game-installs-at-lans-using-nginx/
> In general I think that this change is interesting, but there are
> some rough edges. See below for various comments.
>
>> 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;
> Just using ctx->content_length here may be a better option.
Sorry I'm not sure I follow you correctly, do you mean set the old
content_length local from ctx->content_lenght keeping the diff minimal?
> @@ -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;
> }
> ...
>
> Additionally, it looks like appropriate modifications to
> ngx_http_range_multipart_header() are missing. This will result
> in unneeded/confusing Content-Range header in multipart/ranges
> responses produced from 206 responses.
Nice catch will need to work on this one.
>> + return NGX_HTTP_RANGE_NOT_SATISFIABLE;
>> + }
> NGX_HTTP_RANGE_NOT_SATISFIABLE is wrong and confusing here. It's
> not checked/used by the calling code, but nevertheless shouldn't
> be here.
>
> The same applies to other uses of NGX_HTTP_RANGE_NOT_SATISFIABLE
> in this function.
Will look into these
>> { ngx_null_string, NULL, 0, NULL, 0, 0 }
>> };
>>
>> @@ -4516,37 +4521,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)
>> -
> Style, empty lines should be here.
I removed as it was inconsistent with the function above, could you
confirm the details of this style requirement please so I know for the
future?
>
>> if (r->cached) {
>> r->allow_ranges = 1;
>> - return NGX_OK;
>> + if (offsetof(ngx_http_headers_out_t, accept_ranges) == offset) {
>> + return NGX_OK;
>> + }
>> }
> It may be better idea to introduce a separate copy function
> instead.
>
>>
>> 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);
> I don't think that ngx_http_upstream_copy_header_line() worth it.
I thought it would be wasteful to copy the function instead of making
the small amendments to make it more generic and reuse
ngx_http_upstream_copy_header_line instead of a hand rolled version of it.
Its easy enough to make it independent if you think that's the better
option?
Regards
Steve
More information about the nginx-devel
mailing list