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

Commit 2fcfc6b

Browse files
authored
Add EnableRangeProcessing (#6895)
Addresses #6780
1 parent eeac999 commit 2fcfc6b

16 files changed

+813
-176
lines changed

src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs

Lines changed: 331 additions & 0 deletions
Large diffs are not rendered by default.

src/Microsoft.AspNetCore.Mvc.Core/FileResult.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,10 @@ public string FileDownloadName
5252
/// Gets or sets the etag associated with the <see cref="FileResult"/>.
5353
/// </summary>
5454
public EntityTagHeaderValue EntityTag { get; set; }
55+
56+
/// <summary>
57+
/// Gets or sets the value that enables range processing for the <see cref="FileResult"/>.
58+
/// </summary>
59+
public bool EnableRangeProcessing { get; set; }
5560
}
5661
}

src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileContentResultExecutor.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public virtual Task ExecuteAsync(ActionContext context, FileContentResult result
3333
context,
3434
result,
3535
result.FileContents.Length,
36+
result.EnableRangeProcessing,
3637
result.LastModified,
3738
result.EntityTag);
3839

src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileResultExecutorBase.cs

Lines changed: 63 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ protected virtual (RangeItemHeaderValue range, long rangeLength, bool serveBody)
4141
ActionContext context,
4242
FileResult result,
4343
long? fileLength,
44+
bool enableRangeProcessing,
4445
DateTimeOffset? lastModified = null,
4546
EntityTagHeaderValue etag = null)
4647
{
@@ -59,54 +60,55 @@ protected virtual (RangeItemHeaderValue range, long rangeLength, bool serveBody)
5960

6061
var request = context.HttpContext.Request;
6162
var httpRequestHeaders = request.GetTypedHeaders();
63+
var preconditionState = GetPreconditionState(httpRequestHeaders, lastModified, etag);
64+
6265
var response = context.HttpContext.Response;
63-
var httpResponseHeaders = response.GetTypedHeaders();
64-
if (lastModified.HasValue)
65-
{
66-
httpResponseHeaders.LastModified = lastModified;
67-
}
68-
if (etag != null)
69-
{
70-
httpResponseHeaders.ETag = etag;
71-
}
66+
SetLastModifiedAndEtagHeaders(response, lastModified, etag);
7267

7368
var serveBody = !HttpMethods.IsHead(request.Method);
74-
var preconditionState = GetPreconditionState(context, httpRequestHeaders, lastModified, etag);
69+
70+
// Short circuit if the preconditional headers process to 304 (NotModified) or 412 (PreconditionFailed)
7571
if (preconditionState == PreconditionState.NotModified)
7672
{
7773
serveBody = false;
7874
response.StatusCode = StatusCodes.Status304NotModified;
75+
return (range: null, rangeLength: 0, serveBody);
7976
}
8077
else if (preconditionState == PreconditionState.PreconditionFailed)
8178
{
8279
serveBody = false;
8380
response.StatusCode = StatusCodes.Status412PreconditionFailed;
81+
return (range: null, rangeLength: 0, serveBody);
8482
}
8583

8684
if (fileLength.HasValue)
8785
{
88-
SetAcceptRangeHeader(context);
89-
// Assuming the request is not a range request, the Content-Length header is set to the length of the entire file.
86+
// Assuming the request is not a range request, and the response body is not empty, the Content-Length header is set to
87+
// the length of the entire file.
9088
// If the request is a valid range request, this header is overwritten with the length of the range as part of the
9189
// range processing (see method SetContentLength).
9290
if (serveBody)
9391
{
9492
response.ContentLength = fileLength.Value;
9593
}
96-
if (HttpMethods.IsHead(request.Method) || HttpMethods.IsGet(request.Method))
94+
95+
// Handle range request
96+
if (enableRangeProcessing)
9797
{
98-
if ((preconditionState == PreconditionState.Unspecified ||
99-
preconditionState == PreconditionState.ShouldProcess))
98+
SetAcceptRangeHeader(response);
99+
100+
// If the request method is HEAD or GET, PreconditionState is Unspecified or ShouldProcess, and IfRange header is valid,
101+
// range should be processed and Range headers should be set
102+
if ((HttpMethods.IsHead(request.Method) || HttpMethods.IsGet(request.Method))
103+
&& (preconditionState == PreconditionState.Unspecified || preconditionState == PreconditionState.ShouldProcess)
104+
&& (IfRangeValid(httpRequestHeaders, lastModified, etag)))
100105
{
101-
if (IfRangeValid(context, httpRequestHeaders, lastModified, etag))
102-
{
103-
return SetRangeHeaders(context, httpRequestHeaders, fileLength.Value);
104-
}
106+
return SetRangeHeaders(context, httpRequestHeaders, fileLength.Value);
105107
}
106108
}
107109
}
108110

109-
return (range: null, rangeLength: 0, serveBody: serveBody);
111+
return (range: null, rangeLength: 0, serveBody);
110112
}
111113

112114
private static void SetContentType(ActionContext context, FileResult result)
@@ -130,38 +132,25 @@ private static void SetContentDispositionHeader(ActionContext context, FileResul
130132
}
131133
}
132134

133-
private static void SetAcceptRangeHeader(ActionContext context)
134-
{
135-
var response = context.HttpContext.Response;
136-
response.Headers[HeaderNames.AcceptRanges] = AcceptRangeHeaderValue;
137-
}
138-
139-
private static PreconditionState GetEtagMatchState(
140-
IList<EntityTagHeaderValue> etagHeader,
141-
EntityTagHeaderValue etag,
142-
PreconditionState matchFoundState,
143-
PreconditionState matchNotFoundState)
135+
private static void SetLastModifiedAndEtagHeaders(HttpResponse response, DateTimeOffset? lastModified, EntityTagHeaderValue etag)
144136
{
145-
if (etagHeader != null && etagHeader.Any())
137+
var httpResponseHeaders = response.GetTypedHeaders();
138+
if (lastModified.HasValue)
146139
{
147-
var state = matchNotFoundState;
148-
foreach (var entityTag in etagHeader)
149-
{
150-
if (entityTag.Equals(EntityTagHeaderValue.Any) || entityTag.Compare(etag, useStrongComparison: true))
151-
{
152-
state = matchFoundState;
153-
break;
154-
}
155-
}
156-
157-
return state;
140+
httpResponseHeaders.LastModified = lastModified;
158141
}
142+
if (etag != null)
143+
{
144+
httpResponseHeaders.ETag = etag;
145+
}
146+
}
159147

160-
return PreconditionState.Unspecified;
148+
private static void SetAcceptRangeHeader(HttpResponse response)
149+
{
150+
response.Headers[HeaderNames.AcceptRanges] = AcceptRangeHeaderValue;
161151
}
162152

163153
internal static bool IfRangeValid(
164-
ActionContext context,
165154
RequestHeaders httpRequestHeaders,
166155
DateTimeOffset? lastModified = null,
167156
EntityTagHeaderValue etag = null)
@@ -193,7 +182,6 @@ internal static bool IfRangeValid(
193182

194183
// Internal for testing
195184
internal static PreconditionState GetPreconditionState(
196-
ActionContext context,
197185
RequestHeaders httpRequestHeaders,
198186
DateTimeOffset? lastModified = null,
199187
EntityTagHeaderValue etag = null)
@@ -247,6 +235,30 @@ internal static PreconditionState GetPreconditionState(
247235
return state;
248236
}
249237

238+
private static PreconditionState GetEtagMatchState(
239+
IList<EntityTagHeaderValue> etagHeader,
240+
EntityTagHeaderValue etag,
241+
PreconditionState matchFoundState,
242+
PreconditionState matchNotFoundState)
243+
{
244+
if (etagHeader != null && etagHeader.Any())
245+
{
246+
var state = matchNotFoundState;
247+
foreach (var entityTag in etagHeader)
248+
{
249+
if (entityTag.Equals(EntityTagHeaderValue.Any) || entityTag.Compare(etag, useStrongComparison: true))
250+
{
251+
state = matchFoundState;
252+
break;
253+
}
254+
}
255+
256+
return state;
257+
}
258+
259+
return PreconditionState.Unspecified;
260+
}
261+
250262
private static PreconditionState GetMaxPreconditionState(params PreconditionState[] states)
251263
{
252264
var max = PreconditionState.Unspecified;
@@ -281,6 +293,7 @@ private static (RangeItemHeaderValue range, long rangeLength, bool serveBody) Se
281293
return (range: null, rangeLength: 0, serveBody: true);
282294
}
283295

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

308+
response.StatusCode = StatusCodes.Status206PartialContent;
295309
httpResponseHeaders.ContentRange = new ContentRangeHeaderValue(
296310
range.From.Value,
297311
range.To.Value,
298312
fileLength);
299313

300-
response.StatusCode = StatusCodes.Status206PartialContent;
301314
// Overwrite the Content-Length header for valid range requests with the range length.
302-
var rangeLength = SetContentLength(context, range);
315+
var rangeLength = SetContentLength(response, range);
316+
303317
return (range, rangeLength, serveBody: true);
304318
}
305319

306-
private static long SetContentLength(ActionContext context, RangeItemHeaderValue range)
320+
private static long SetContentLength(HttpResponse response, RangeItemHeaderValue range)
307321
{
308322
var start = range.From.Value;
309323
var end = range.To.Value;
310324
var length = end - start + 1;
311-
var response = context.HttpContext.Response;
312325
response.ContentLength = length;
313326
return length;
314327
}

src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileStreamResultExecutor.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public virtual Task ExecuteAsync(ActionContext context, FileStreamResult result)
3838
context,
3939
result,
4040
fileLength,
41+
result.EnableRangeProcessing,
4142
result.LastModified,
4243
result.EntityTag);
4344

src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/PhysicalFileResultExecutor.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public virtual Task ExecuteAsync(ActionContext context, PhysicalFileResult resul
4444
context,
4545
result,
4646
fileInfo.Length,
47+
result.EnableRangeProcessing,
4748
lastModified,
4849
result.EntityTag);
4950

src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/VirtualFileResultExecutor.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
namespace Microsoft.AspNetCore.Mvc.Infrastructure
1616
{
17-
public class VirtualFileResultExecutor : FileResultExecutorBase , IActionResultExecutor<VirtualFileResult>
17+
public class VirtualFileResultExecutor : FileResultExecutorBase, IActionResultExecutor<VirtualFileResult>
1818
{
1919
private readonly IHostingEnvironment _hostingEnvironment;
2020

@@ -54,6 +54,7 @@ public virtual Task ExecuteAsync(ActionContext context, VirtualFileResult result
5454
context,
5555
result,
5656
fileInfo.Length,
57+
result.EnableRangeProcessing,
5758
lastModified,
5859
result.EntityTag);
5960

0 commit comments

Comments
 (0)