Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Add EnableRangeProcessing #6895

Merged
merged 1 commit into from
Sep 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
331 changes: 331 additions & 0 deletions src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions src/Microsoft.AspNetCore.Mvc.Core/FileResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,10 @@ public string FileDownloadName
/// Gets or sets the etag associated with the <see cref="FileResult"/>.
/// </summary>
public EntityTagHeaderValue EntityTag { get; set; }

/// <summary>
/// Gets or sets the value that enables range processing for the <see cref="FileResult"/>.
/// </summary>
public bool EnableRangeProcessing { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public virtual Task ExecuteAsync(ActionContext context, FileContentResult result
context,
result,
result.FileContents.Length,
result.EnableRangeProcessing,
result.LastModified,
result.EntityTag);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ protected virtual (RangeItemHeaderValue range, long rangeLength, bool serveBody)
ActionContext context,
FileResult result,
long? fileLength,
bool enableRangeProcessing,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is .Infrastructure considered public API? the class is public and the method is protected, so people could derive from it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was .Internal until recently.

@Eilon is Infrastructure just a name change and is considered internal still?

Copy link
Member

@rynowak rynowak Sep 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jbagga was this in .Infrastructure in 2.0.0? That's the salient point

If this class was in .Internal in 2.0.0 then it has no support bar in 2.0.0 and there is no breaking change 😆

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or rather, it's a change that we're willing to make

Copy link
Contributor Author

@jbagga jbagga Sep 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DateTimeOffset? lastModified = null,
EntityTagHeaderValue etag = null)
{
Expand All @@ -59,54 +60,55 @@ protected virtual (RangeItemHeaderValue range, long rangeLength, bool serveBody)

var request = context.HttpContext.Request;
var httpRequestHeaders = request.GetTypedHeaders();
var preconditionState = GetPreconditionState(httpRequestHeaders, lastModified, etag);

var response = context.HttpContext.Response;
var httpResponseHeaders = response.GetTypedHeaders();
if (lastModified.HasValue)
{
httpResponseHeaders.LastModified = lastModified;
}
if (etag != null)
{
httpResponseHeaders.ETag = etag;
}
SetLastModifiedAndEtagHeaders(response, lastModified, etag);

var serveBody = !HttpMethods.IsHead(request.Method);
var preconditionState = GetPreconditionState(context, httpRequestHeaders, lastModified, etag);

// Short circuit if the preconditional headers process to 304 (NotModified) or 412 (PreconditionFailed)
if (preconditionState == PreconditionState.NotModified)
{
serveBody = false;
response.StatusCode = StatusCodes.Status304NotModified;
return (range: null, rangeLength: 0, serveBody);
}
else if (preconditionState == PreconditionState.PreconditionFailed)
{
serveBody = false;
response.StatusCode = StatusCodes.Status412PreconditionFailed;
return (range: null, rangeLength: 0, serveBody);
}

if (fileLength.HasValue)
{
SetAcceptRangeHeader(context);
// Assuming the request is not a range request, the Content-Length header is set to the length of the entire file.
// Assuming the request is not a range request, and the response body is not empty, the Content-Length header is set to
// the length of the entire file.
// If the request is a valid range request, this header is overwritten with the length of the range as part of the
// range processing (see method SetContentLength).
if (serveBody)
{
response.ContentLength = fileLength.Value;
}
if (HttpMethods.IsHead(request.Method) || HttpMethods.IsGet(request.Method))

// Handle range request
if (enableRangeProcessing)
{
if ((preconditionState == PreconditionState.Unspecified ||
preconditionState == PreconditionState.ShouldProcess))
SetAcceptRangeHeader(response);

// If the request method is HEAD or GET, PreconditionState is Unspecified or ShouldProcess, and IfRange header is valid,
// range should be processed and Range headers should be set
if ((HttpMethods.IsHead(request.Method) || HttpMethods.IsGet(request.Method))
&& (preconditionState == PreconditionState.Unspecified || preconditionState == PreconditionState.ShouldProcess)
&& (IfRangeValid(httpRequestHeaders, lastModified, etag)))
{
if (IfRangeValid(context, httpRequestHeaders, lastModified, etag))
{
return SetRangeHeaders(context, httpRequestHeaders, fileLength.Value);
}
return SetRangeHeaders(context, httpRequestHeaders, fileLength.Value);
}
}
}

return (range: null, rangeLength: 0, serveBody: serveBody);
return (range: null, rangeLength: 0, serveBody);
}

private static void SetContentType(ActionContext context, FileResult result)
Expand All @@ -130,38 +132,25 @@ private static void SetContentDispositionHeader(ActionContext context, FileResul
}
}

private static void SetAcceptRangeHeader(ActionContext context)
{
var response = context.HttpContext.Response;
response.Headers[HeaderNames.AcceptRanges] = AcceptRangeHeaderValue;
}

private static PreconditionState GetEtagMatchState(
IList<EntityTagHeaderValue> etagHeader,
EntityTagHeaderValue etag,
PreconditionState matchFoundState,
PreconditionState matchNotFoundState)
private static void SetLastModifiedAndEtagHeaders(HttpResponse response, DateTimeOffset? lastModified, EntityTagHeaderValue etag)
{
if (etagHeader != null && etagHeader.Any())
var httpResponseHeaders = response.GetTypedHeaders();
if (lastModified.HasValue)
{
var state = matchNotFoundState;
foreach (var entityTag in etagHeader)
{
if (entityTag.Equals(EntityTagHeaderValue.Any) || entityTag.Compare(etag, useStrongComparison: true))
{
state = matchFoundState;
break;
}
}

return state;
httpResponseHeaders.LastModified = lastModified;
}
if (etag != null)
{
httpResponseHeaders.ETag = etag;
}
}

return PreconditionState.Unspecified;
private static void SetAcceptRangeHeader(HttpResponse response)
{
response.Headers[HeaderNames.AcceptRanges] = AcceptRangeHeaderValue;
}

internal static bool IfRangeValid(
ActionContext context,
RequestHeaders httpRequestHeaders,
DateTimeOffset? lastModified = null,
EntityTagHeaderValue etag = null)
Expand Down Expand Up @@ -193,7 +182,6 @@ internal static bool IfRangeValid(

// Internal for testing
internal static PreconditionState GetPreconditionState(
ActionContext context,
RequestHeaders httpRequestHeaders,
DateTimeOffset? lastModified = null,
EntityTagHeaderValue etag = null)
Expand Down Expand Up @@ -247,6 +235,30 @@ internal static PreconditionState GetPreconditionState(
return state;
}

private static PreconditionState GetEtagMatchState(
IList<EntityTagHeaderValue> etagHeader,
EntityTagHeaderValue etag,
PreconditionState matchFoundState,
PreconditionState matchNotFoundState)
{
if (etagHeader != null && etagHeader.Any())
{
var state = matchNotFoundState;
foreach (var entityTag in etagHeader)
{
if (entityTag.Equals(EntityTagHeaderValue.Any) || entityTag.Compare(etag, useStrongComparison: true))
{
state = matchFoundState;
break;
}
}

return state;
}

return PreconditionState.Unspecified;
}

private static PreconditionState GetMaxPreconditionState(params PreconditionState[] states)
{
var max = PreconditionState.Unspecified;
Expand Down Expand Up @@ -281,6 +293,7 @@ private static (RangeItemHeaderValue range, long rangeLength, bool serveBody) Se
return (range: null, rangeLength: 0, serveBody: true);
}

// Requested range is not satisfiable
if (range == null)
{
// 14.16 Content-Range - A server sending a response with status code 416 (Requested range not satisfiable)
Expand All @@ -292,23 +305,23 @@ private static (RangeItemHeaderValue range, long rangeLength, bool serveBody) Se
return (range: null, rangeLength: 0, serveBody: false);
}

response.StatusCode = StatusCodes.Status206PartialContent;
httpResponseHeaders.ContentRange = new ContentRangeHeaderValue(
range.From.Value,
range.To.Value,
fileLength);

response.StatusCode = StatusCodes.Status206PartialContent;
// Overwrite the Content-Length header for valid range requests with the range length.
var rangeLength = SetContentLength(context, range);
var rangeLength = SetContentLength(response, range);

return (range, rangeLength, serveBody: true);
}

private static long SetContentLength(ActionContext context, RangeItemHeaderValue range)
private static long SetContentLength(HttpResponse response, RangeItemHeaderValue range)
{
var start = range.From.Value;
var end = range.To.Value;
var length = end - start + 1;
var response = context.HttpContext.Response;
response.ContentLength = length;
return length;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public virtual Task ExecuteAsync(ActionContext context, FileStreamResult result)
context,
result,
fileLength,
result.EnableRangeProcessing,
result.LastModified,
result.EntityTag);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public virtual Task ExecuteAsync(ActionContext context, PhysicalFileResult resul
context,
result,
fileInfo.Length,
result.EnableRangeProcessing,
lastModified,
result.EntityTag);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

namespace Microsoft.AspNetCore.Mvc.Infrastructure
{
public class VirtualFileResultExecutor : FileResultExecutorBase , IActionResultExecutor<VirtualFileResult>
public class VirtualFileResultExecutor : FileResultExecutorBase, IActionResultExecutor<VirtualFileResult>
{
private readonly IHostingEnvironment _hostingEnvironment;

Expand Down Expand Up @@ -54,6 +54,7 @@ public virtual Task ExecuteAsync(ActionContext context, VirtualFileResult result
context,
result,
fileInfo.Length,
result.EnableRangeProcessing,
lastModified,
result.EntityTag);

Expand Down
Loading