Use strongly typed headers.
This commit is contained in:
parent
82be0d3d4a
commit
212c264ed3
|
|
@ -11,21 +11,6 @@ namespace Microsoft.AspNet.StaticFiles
|
|||
internal const string SendFileVersionKey = "sendfile.Version";
|
||||
internal const string SendFileVersion = "1.0";
|
||||
|
||||
internal const string Location = "Location";
|
||||
internal const string IfMatch = "If-Match";
|
||||
internal const string IfNoneMatch = "If-None-Match";
|
||||
internal const string IfModifiedSince = "If-Modified-Since";
|
||||
internal const string IfUnmodifiedSince = "If-Unmodified-Since";
|
||||
internal const string IfRange = "If-Range";
|
||||
internal const string Range = "Range";
|
||||
internal const string ContentRange = "Content-Range";
|
||||
internal const string LastModified = "Last-Modified";
|
||||
internal const string ETag = "ETag";
|
||||
|
||||
internal const string HttpDateFormat = "r";
|
||||
|
||||
internal const string TextHtmlUtf8 = "text/html; charset=utf-8";
|
||||
|
||||
internal const int Status200Ok = 200;
|
||||
internal const int Status206PartialContent = 206;
|
||||
internal const int Status304NotModified = 304;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
using Microsoft.AspNet.Hosting;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNet.StaticFiles
|
||||
{
|
||||
|
|
@ -65,7 +63,7 @@ namespace Microsoft.AspNet.StaticFiles
|
|||
if (!Helpers.PathEndsInSlash(context.Request.Path))
|
||||
{
|
||||
context.Response.StatusCode = 301;
|
||||
context.Response.Headers[Constants.Location] = context.Request.PathBase + context.Request.Path + "/" + context.Request.QueryString;
|
||||
context.Response.Headers[HeaderNames.Location] = context.Request.PathBase + context.Request.Path + "/" + context.Request.QueryString;
|
||||
return Constants.CompletedTask;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
using Microsoft.AspNet.Hosting;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNet.StaticFiles
|
||||
{
|
||||
|
|
@ -57,7 +57,7 @@ namespace Microsoft.AspNet.StaticFiles
|
|||
if (!Helpers.PathEndsInSlash(context.Request.Path))
|
||||
{
|
||||
context.Response.StatusCode = 301;
|
||||
context.Response.Headers[Constants.Location] = context.Request.PathBase + context.Request.Path + "/" + context.Request.QueryString;
|
||||
context.Response.Headers[HeaderNames.Location] = context.Request.PathBase + context.Request.Path + "/" + context.Request.QueryString;
|
||||
return Constants.CompletedTask;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.StaticFiles
|
||||
|
|
@ -45,15 +43,5 @@ namespace Microsoft.AspNet.StaticFiles
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool TryParseHttpDate(string dateString, out DateTimeOffset parsedDate)
|
||||
{
|
||||
return DateTimeOffset.TryParseExact(dateString, Constants.HttpDateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out parsedDate);
|
||||
}
|
||||
|
||||
internal static string ResolveRootPath(string webRoot, PathString path)
|
||||
{
|
||||
return Path.GetFullPath(Path.Combine(webRoot, path.Value ?? string.Empty));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ namespace Microsoft.AspNet.StaticFiles
|
|||
/// </summary>
|
||||
public class HtmlDirectoryFormatter : IDirectoryFormatter
|
||||
{
|
||||
private const string TextHtmlUtf8 = "text/html; charset=utf-8";
|
||||
|
||||
/// <summary>
|
||||
/// Generates an HTML view for a directory.
|
||||
/// </summary>
|
||||
|
|
@ -32,7 +34,7 @@ namespace Microsoft.AspNet.StaticFiles
|
|||
throw new ArgumentNullException("contents");
|
||||
}
|
||||
|
||||
context.Response.ContentType = Constants.TextHtmlUtf8;
|
||||
context.Response.ContentType = TextHtmlUtf8;
|
||||
|
||||
if (Helpers.IsHeadMethod(context.Request.Method))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,107 +3,23 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNet.StaticFiles.Infrastructure
|
||||
{
|
||||
internal static class RangeHelpers
|
||||
{
|
||||
// Examples:
|
||||
// bytes=0-499
|
||||
// bytes=500-
|
||||
// bytes=-500
|
||||
// bytes=0-0,-1
|
||||
// bytes=500-600,601-999
|
||||
// Any individual bad range fails the whole parse and the header should be ignored.
|
||||
internal static bool TryParseRanges(string rangeHeader, out IList<Tuple<long?, long?>> parsedRanges)
|
||||
{
|
||||
parsedRanges = null;
|
||||
if (string.IsNullOrWhiteSpace(rangeHeader)
|
||||
|| !rangeHeader.StartsWith("bytes=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string[] subRanges = rangeHeader.Substring("bytes=".Length).Replace(" ", string.Empty).Split(',');
|
||||
|
||||
List<Tuple<long?, long?>> ranges = new List<Tuple<long?, long?>>();
|
||||
|
||||
for (int i = 0; i < subRanges.Length; i++)
|
||||
{
|
||||
long? first = null, second = null;
|
||||
string subRange = subRanges[i];
|
||||
int dashIndex = subRange.IndexOf('-');
|
||||
if (dashIndex < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (dashIndex == 0)
|
||||
{
|
||||
// -500
|
||||
string remainder = subRange.Substring(1);
|
||||
if (!TryParseLong(remainder, out second))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (dashIndex == (subRange.Length - 1))
|
||||
{
|
||||
// 500-
|
||||
string remainder = subRange.Substring(0, subRange.Length - 1);
|
||||
if (!TryParseLong(remainder, out first))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 0-499
|
||||
string firstString = subRange.Substring(0, dashIndex);
|
||||
string secondString = subRange.Substring(dashIndex + 1, subRange.Length - dashIndex - 1);
|
||||
if (!TryParseLong(firstString, out first) || !TryParseLong(secondString, out second)
|
||||
|| first.Value > second.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ranges.Add(new Tuple<long?, long?>(first, second));
|
||||
}
|
||||
|
||||
if (ranges.Count > 0)
|
||||
{
|
||||
parsedRanges = ranges;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryParseLong(string input, out long? result)
|
||||
{
|
||||
int temp;
|
||||
if (!string.IsNullOrWhiteSpace(input)
|
||||
&& int.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out temp))
|
||||
{
|
||||
result = temp;
|
||||
return true;
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 14.35.1 Byte Ranges - If a syntactically valid byte-range-set includes at least one byte-range-spec whose
|
||||
// first-byte-pos is less than the current length of the entity-body, or at least one suffix-byte-range-spec
|
||||
// with a non-zero suffix-length, then the byte-range-set is satisfiable.
|
||||
// Adjusts ranges to be absolute and within bounds.
|
||||
internal static IList<Tuple<long, long>> NormalizeRanges(IList<Tuple<long?, long?>> ranges, long length)
|
||||
internal static IList<RangeItemHeaderValue> NormalizeRanges(ICollection<RangeItemHeaderValue> ranges, long length)
|
||||
{
|
||||
IList<Tuple<long, long>> normalizedRanges = new List<Tuple<long, long>>(ranges.Count);
|
||||
for (int i = 0; i < ranges.Count; i++)
|
||||
IList<RangeItemHeaderValue> normalizedRanges = new List<RangeItemHeaderValue>(ranges.Count);
|
||||
foreach (var range in ranges)
|
||||
{
|
||||
Tuple<long?, long?> range = ranges[i];
|
||||
long? start = range.Item1, end = range.Item2;
|
||||
long? start = range.From;
|
||||
long? end = range.To;
|
||||
|
||||
// X-[Y]
|
||||
if (start.HasValue)
|
||||
|
|
@ -131,7 +47,7 @@ namespace Microsoft.AspNet.StaticFiles.Infrastructure
|
|||
start = length - bytes;
|
||||
end = start + bytes - 1;
|
||||
}
|
||||
normalizedRanges.Add(new Tuple<long, long>(start.Value, end.Value));
|
||||
normalizedRanges.Add(new RangeItemHeaderValue(start.Value, end.Value));
|
||||
}
|
||||
return normalizedRanges;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
|
|
@ -14,4 +14,9 @@
|
|||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<UserProperties project_1json__JSONSchema="http://www.asp.net/media/4878834/project.json" />
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
||||
|
|
@ -4,14 +4,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Headers;
|
||||
using Microsoft.AspNet.HttpFeature;
|
||||
using Microsoft.AspNet.StaticFiles.Infrastructure;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNet.StaticFiles
|
||||
{
|
||||
|
|
@ -31,16 +33,17 @@ namespace Microsoft.AspNet.StaticFiles
|
|||
private IFileInfo _fileInfo;
|
||||
private long _length;
|
||||
private DateTimeOffset _lastModified;
|
||||
private string _lastModifiedString;
|
||||
private string _etag;
|
||||
private string _etagQuoted;
|
||||
private EntityTagHeaderValue _etag;
|
||||
|
||||
private RequestHeaders _requestHeaders;
|
||||
private ResponseHeaders _responseHeaders;
|
||||
|
||||
private PreconditionState _ifMatchState;
|
||||
private PreconditionState _ifNoneMatchState;
|
||||
private PreconditionState _ifModifiedSinceState;
|
||||
private PreconditionState _ifUnmodifiedSinceState;
|
||||
|
||||
private IList<Tuple<long, long>> _ranges;
|
||||
private IList<RangeItemHeaderValue> _ranges;
|
||||
|
||||
public StaticFileContext(HttpContext context, StaticFileOptions options, PathString matchUrl, ILogger logger)
|
||||
{
|
||||
|
|
@ -50,6 +53,8 @@ namespace Microsoft.AspNet.StaticFiles
|
|||
_request = context.Request;
|
||||
_response = context.Response;
|
||||
_logger = logger;
|
||||
_requestHeaders = _request.GetTypedHeaders();
|
||||
_responseHeaders = _response.GetTypedHeaders();
|
||||
|
||||
_method = null;
|
||||
_isGet = false;
|
||||
|
|
@ -60,8 +65,6 @@ namespace Microsoft.AspNet.StaticFiles
|
|||
_length = 0;
|
||||
_lastModified = new DateTimeOffset();
|
||||
_etag = null;
|
||||
_etagQuoted = null;
|
||||
_lastModifiedString = null;
|
||||
_ifMatchState = PreconditionState.Unspecified;
|
||||
_ifNoneMatchState = PreconditionState.Unspecified;
|
||||
_ifModifiedSinceState = PreconditionState.Unspecified;
|
||||
|
|
@ -86,7 +89,7 @@ namespace Microsoft.AspNet.StaticFiles
|
|||
{
|
||||
get { return _ranges != null; }
|
||||
}
|
||||
|
||||
|
||||
public string SubPath
|
||||
{
|
||||
get { return _subPath.Value; }
|
||||
|
|
@ -132,11 +135,9 @@ namespace Microsoft.AspNet.StaticFiles
|
|||
DateTimeOffset last = _fileInfo.LastModified;
|
||||
// Truncate to the second.
|
||||
_lastModified = new DateTimeOffset(last.Year, last.Month, last.Day, last.Hour, last.Minute, last.Second, last.Offset);
|
||||
_lastModifiedString = _lastModified.ToString(Constants.HttpDateFormat, CultureInfo.InvariantCulture);
|
||||
|
||||
long etagHash = _lastModified.ToFileTime() ^ _length;
|
||||
_etag = Convert.ToString(etagHash, 16);
|
||||
_etagQuoted = '\"' + _etag + '\"';
|
||||
_etag = new EntityTagHeaderValue('\"' + Convert.ToString(etagHash, 16) + '\"');
|
||||
}
|
||||
return _fileInfo.Exists;
|
||||
}
|
||||
|
|
@ -153,14 +154,13 @@ namespace Microsoft.AspNet.StaticFiles
|
|||
private void ComputeIfMatch()
|
||||
{
|
||||
// 14.24 If-Match
|
||||
IList<string> ifMatch = _request.Headers.GetCommaSeparatedValues(Constants.IfMatch); // Removes quotes
|
||||
if (ifMatch != null)
|
||||
var ifMatch = _requestHeaders.IfMatch;
|
||||
if (ifMatch != null && ifMatch.Any())
|
||||
{
|
||||
_ifMatchState = PreconditionState.PreconditionFailed;
|
||||
foreach (var segment in ifMatch)
|
||||
foreach (var etag in ifMatch)
|
||||
{
|
||||
if (segment.Equals("*", StringComparison.Ordinal)
|
||||
|| segment.Equals(_etag, StringComparison.Ordinal))
|
||||
if (etag.Equals(EntityTagHeaderValue.Any) || etag.Equals(_etag))
|
||||
{
|
||||
_ifMatchState = PreconditionState.ShouldProcess;
|
||||
break;
|
||||
|
|
@ -169,14 +169,13 @@ namespace Microsoft.AspNet.StaticFiles
|
|||
}
|
||||
|
||||
// 14.26 If-None-Match
|
||||
IList<string> ifNoneMatch = _request.Headers.GetCommaSeparatedValues(Constants.IfNoneMatch);
|
||||
if (ifNoneMatch != null)
|
||||
var ifNoneMatch = _requestHeaders.IfNoneMatch;
|
||||
if (ifNoneMatch != null && ifNoneMatch.Any())
|
||||
{
|
||||
_ifNoneMatchState = PreconditionState.ShouldProcess;
|
||||
foreach (var segment in ifNoneMatch)
|
||||
foreach (var etag in ifNoneMatch)
|
||||
{
|
||||
if (segment.Equals("*", StringComparison.Ordinal)
|
||||
|| segment.Equals(_etag, StringComparison.Ordinal))
|
||||
if (etag.Equals(EntityTagHeaderValue.Any) || etag.Equals(_etag))
|
||||
{
|
||||
_ifNoneMatchState = PreconditionState.NotModified;
|
||||
break;
|
||||
|
|
@ -188,18 +187,16 @@ namespace Microsoft.AspNet.StaticFiles
|
|||
private void ComputeIfModifiedSince()
|
||||
{
|
||||
// 14.25 If-Modified-Since
|
||||
string ifModifiedSinceString = _request.Headers.Get(Constants.IfModifiedSince);
|
||||
DateTimeOffset ifModifiedSince;
|
||||
if (Helpers.TryParseHttpDate(ifModifiedSinceString, out ifModifiedSince))
|
||||
var ifModifiedSince = _requestHeaders.IfModifiedSince;
|
||||
if (ifModifiedSince.HasValue)
|
||||
{
|
||||
bool modified = ifModifiedSince < _lastModified;
|
||||
_ifModifiedSinceState = modified ? PreconditionState.ShouldProcess : PreconditionState.NotModified;
|
||||
}
|
||||
|
||||
// 14.28 If-Unmodified-Since
|
||||
string ifUnmodifiedSinceString = _request.Headers.Get(Constants.IfUnmodifiedSince);
|
||||
DateTimeOffset ifUnmodifiedSince;
|
||||
if (Helpers.TryParseHttpDate(ifUnmodifiedSinceString, out ifUnmodifiedSince))
|
||||
var ifUnmodifiedSince = _requestHeaders.IfUnmodifiedSince;
|
||||
if (ifUnmodifiedSince.HasValue)
|
||||
{
|
||||
bool unmodified = ifUnmodifiedSince >= _lastModified;
|
||||
_ifUnmodifiedSinceState = unmodified ? PreconditionState.ShouldProcess : PreconditionState.PreconditionFailed;
|
||||
|
|
@ -218,44 +215,41 @@ namespace Microsoft.AspNet.StaticFiles
|
|||
return;
|
||||
}
|
||||
|
||||
string rangeHeader = _request.Headers.Get(Constants.Range);
|
||||
IList<Tuple<long?, long?>> ranges;
|
||||
if (!RangeHelpers.TryParseRanges(rangeHeader, out ranges))
|
||||
var rangeHeader = _requestHeaders.Range;
|
||||
if (rangeHeader == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ranges.Count > 1)
|
||||
if (rangeHeader.Ranges.Count > 1)
|
||||
{
|
||||
// multiple range headers not yet supported
|
||||
_logger.WriteWarning("Multiple range headers not yet supported, {0} ranges in header", ranges.Count.ToString());
|
||||
// The spec allows for multiple ranges but we choose not to support them because the client may request
|
||||
// very strange ranges (e.g. each byte separately, overlapping ranges, etc.) that could negatively
|
||||
// impact the server. Ignore the header and serve the response normally.
|
||||
_logger.WriteWarning("Multiple ranges are not allowed: '{0}'", rangeHeader.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
// 14.27 If-Range
|
||||
string ifRangeHeader = _request.Headers.Get(Constants.IfRange);
|
||||
if (!string.IsNullOrWhiteSpace(ifRangeHeader))
|
||||
var ifRangeHeader = _requestHeaders.IfRange;
|
||||
if (ifRangeHeader != null)
|
||||
{
|
||||
// If the validator given in the If-Range header field matches the
|
||||
// current validator for the selected representation of the target
|
||||
// resource, then the server SHOULD process the Range header field as
|
||||
// requested. If the validator does not match, the server MUST ignore
|
||||
// the Range header field.
|
||||
DateTimeOffset ifRangeLastModified;
|
||||
bool ignoreRangeHeader = false;
|
||||
if (Helpers.TryParseHttpDate(ifRangeHeader, out ifRangeLastModified))
|
||||
if (ifRangeHeader.LastModified.HasValue)
|
||||
{
|
||||
if (_lastModified > ifRangeLastModified)
|
||||
if (_lastModified > ifRangeHeader.LastModified)
|
||||
{
|
||||
ignoreRangeHeader = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (ifRangeHeader.EntityTag != null && !_etag.Equals(ifRangeHeader.EntityTag))
|
||||
{
|
||||
if (!_etagQuoted.Equals(ifRangeHeader))
|
||||
{
|
||||
ignoreRangeHeader = true;
|
||||
}
|
||||
ignoreRangeHeader = true;
|
||||
}
|
||||
if (ignoreRangeHeader)
|
||||
{
|
||||
|
|
@ -263,7 +257,7 @@ namespace Microsoft.AspNet.StaticFiles
|
|||
}
|
||||
}
|
||||
|
||||
_ranges = RangeHelpers.NormalizeRanges(ranges, _length);
|
||||
_ranges = RangeHelpers.NormalizeRanges(rangeHeader.Ranges, _length);
|
||||
}
|
||||
|
||||
public void ApplyResponseHeaders(int statusCode)
|
||||
|
|
@ -277,8 +271,9 @@ namespace Microsoft.AspNet.StaticFiles
|
|||
{
|
||||
_response.ContentType = _contentType;
|
||||
}
|
||||
_response.Headers.Set(Constants.LastModified, _lastModifiedString);
|
||||
_response.Headers.Set(Constants.ETag, _etagQuoted);
|
||||
_responseHeaders.LastModified = _lastModified;
|
||||
_responseHeaders.ETag = _etag;
|
||||
_responseHeaders.Headers[HeaderNames.AcceptRanges] = "bytes";
|
||||
}
|
||||
if (statusCode == Constants.Status200Ok)
|
||||
{
|
||||
|
|
@ -361,7 +356,7 @@ namespace Microsoft.AspNet.StaticFiles
|
|||
// 14.16 Content-Range - A server sending a response with status code 416 (Requested range not satisfiable)
|
||||
// SHOULD include a Content-Range field with a byte-range-resp-spec of "*". The instance-length specifies
|
||||
// the current length of the selected resource. e.g. */length
|
||||
_response.Headers[Constants.ContentRange] = "bytes */" + _length.ToString(CultureInfo.InvariantCulture);
|
||||
_responseHeaders.ContentRange = new ContentRangeHeaderValue(_length);
|
||||
ApplyResponseHeaders(Constants.Status416RangeNotSatisfiable);
|
||||
_logger.WriteWarning("Range not satisfiable for {0}", SubPath);
|
||||
return;
|
||||
|
|
@ -371,7 +366,7 @@ namespace Microsoft.AspNet.StaticFiles
|
|||
Debug.Assert(_ranges.Count == 1);
|
||||
|
||||
long start, length;
|
||||
_response.Headers[Constants.ContentRange] = ComputeContentRange(_ranges[0], out start, out length);
|
||||
_responseHeaders.ContentRange = ComputeContentRange(_ranges[0], out start, out length);
|
||||
_response.ContentLength = length;
|
||||
ApplyResponseHeaders(Constants.Status206PartialContent);
|
||||
|
||||
|
|
@ -381,7 +376,7 @@ namespace Microsoft.AspNet.StaticFiles
|
|||
{
|
||||
if (_logger.IsEnabled(LogLevel.Verbose))
|
||||
{
|
||||
_logger.WriteVerbose(string.Format("Sending {0} of file {1}", _response.Headers[Constants.ContentRange], physicalPath));
|
||||
_logger.WriteVerbose(string.Format("Sending {0} of file {1}", _response.Headers[HeaderNames.ContentRange], physicalPath));
|
||||
}
|
||||
await sendFile.SendFileAsync(physicalPath, start, length, _context.RequestAborted);
|
||||
return;
|
||||
|
|
@ -393,7 +388,7 @@ namespace Microsoft.AspNet.StaticFiles
|
|||
readStream.Seek(start, SeekOrigin.Begin); // TODO: What if !CanSeek?
|
||||
if (_logger.IsEnabled(LogLevel.Verbose))
|
||||
{
|
||||
_logger.WriteVerbose(string.Format("Copying {0} of file {1} to the response body", _response.Headers[Constants.ContentRange], SubPath));
|
||||
_logger.WriteVerbose(string.Format("Copying {0} of file {1} to the response body", _response.Headers[HeaderNames.ContentRange], SubPath));
|
||||
}
|
||||
await StreamCopyOperation.CopyToAsync(readStream, _response.Body, length, _context.RequestAborted);
|
||||
}
|
||||
|
|
@ -404,12 +399,12 @@ namespace Microsoft.AspNet.StaticFiles
|
|||
}
|
||||
|
||||
// Note: This assumes ranges have been normalized to absolute byte offsets.
|
||||
private string ComputeContentRange(Tuple<long, long> range, out long start, out long length)
|
||||
private ContentRangeHeaderValue ComputeContentRange(RangeItemHeaderValue range, out long start, out long length)
|
||||
{
|
||||
start = range.Item1;
|
||||
long end = range.Item2;
|
||||
start = range.From.Value;
|
||||
long end = range.To.Value;
|
||||
length = end - start + 1;
|
||||
return string.Format(CultureInfo.InvariantCulture, "bytes {0}-{1}/{2}", start, end, _length);
|
||||
return new ContentRangeHeaderValue(start, end, _length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue