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

Change SendFileAsync to use a fallback implementation instead of throwing #497

Closed
wants to merge 4 commits into from
Closed
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
1 change: 1 addition & 0 deletions src/Microsoft.AspNet.Http.Abstractions/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"System.Net.Primitives": "4.0.11-*",
"System.Net.WebSockets": "4.0.0-*",
"System.Reflection.TypeExtensions": "4.0.1-*",
"System.IO": "4.0.11-*",
"System.Runtime": "4.0.21-*",
"System.Runtime.InteropServices": "4.0.21-*",
"System.Security.Claims": "4.0.1-*",
Expand Down

This file was deleted.

123 changes: 0 additions & 123 deletions src/Microsoft.AspNet.Http.Extensions/Resources.resx

This file was deleted.

61 changes: 42 additions & 19 deletions src/Microsoft.AspNet.Http.Extensions/SendFileResponseExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Http.Extensions;
using Microsoft.AspNet.Http.Features;

namespace Microsoft.AspNet.Http
Expand All @@ -14,26 +14,11 @@ namespace Microsoft.AspNet.Http
/// </summary>
public static class SendFileResponseExtensions
{
/// <summary>
/// Checks if the SendFile extension is supported.
/// </summary>
/// <param name="response"></param>
/// <returns>True if sendfile feature exists in the response.</returns>
public static bool SupportsSendFile(this HttpResponse response)
{
if (response == null)
{
throw new ArgumentNullException(nameof(response));
}

return response.HttpContext.Features.Get<IHttpSendFileFeature>() != null;
}

/// <summary>
/// Sends the given file using the SendFile extension.
/// </summary>
/// <param name="response"></param>
/// <param name="fileName"></param>
/// <param name="fileName">The full to the file.</param>
/// <returns></returns>
public static Task SendFileAsync(this HttpResponse response, string fileName)
{
Expand All @@ -54,7 +39,7 @@ public static Task SendFileAsync(this HttpResponse response, string fileName)
/// Sends the given file using the SendFile extension.
/// </summary>
/// <param name="response"></param>
/// <param name="fileName">The full or relative path to the file.</param>
/// <param name="fileName">The full to the file.</param>
/// <param name="offset">The offset in the file.</param>
/// <param name="count">The number of types to send, or null to send the remainder of the file.</param>
/// <param name="cancellationToken"></param>
Expand All @@ -74,10 +59,48 @@ public static Task SendFileAsync(this HttpResponse response, string fileName, lo
var sendFile = response.HttpContext.Features.Get<IHttpSendFileFeature>();
if (sendFile == null)
{
throw new NotSupportedException(Resources.Exception_SendFileNotSupported);
return SendFileAsync(response.Body, fileName, offset, count, cancellationToken);
Copy link
Member

Choose a reason for hiding this comment

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

This fallback makes SupportsSendFile unnecessary, remove it.

Copy link
Member Author

Choose a reason for hiding this comment

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

It's not unnecessary. It's just convenient. But sure.

}

return sendFile.SendFileAsync(fileName, offset, count, cancellationToken);
}

// Not safe for overlapped writes.
private static async Task SendFileAsync(Stream outputStream, string fileName, long offset, long? length, CancellationToken cancel)
{
cancel.ThrowIfCancellationRequested();

var fileInfo = new FileInfo(fileName);
if (offset < 0 || offset > fileInfo.Length)
{
throw new ArgumentOutOfRangeException(nameof(offset), offset, string.Empty);
}

if (length.HasValue &&
(length.Value < 0 || length.Value > fileInfo.Length - offset))
{
throw new ArgumentOutOfRangeException(nameof(length), length, string.Empty);
}

int bufferSize = 1024 * 16;

var fileStream = new FileStream(
fileName,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite,
bufferSize: bufferSize,
options: FileOptions.Asynchronous | FileOptions.SequentialScan);

using (fileStream)
{
fileStream.Seek(offset, SeekOrigin.Begin);

// TODO: Use buffer pool
var buffer = new byte[bufferSize];

await StreamCopyOperation.CopyToAsync(fileStream, buffer, outputStream, length, cancel);
}
}
}
}
57 changes: 57 additions & 0 deletions src/Microsoft.AspNet.Http.Extensions/StreamCopyOperation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) .NET Foundation. 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.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.AspNet.Http
{
// FYI: In most cases the source will be a FileStream and the destination will be to the network.
internal static class StreamCopyOperation
{
internal static async Task CopyToAsync(Stream source, byte[] buffer, Stream destination, long? length, CancellationToken cancel)
{
long? bytesRemaining = length;
Debug.Assert(source != null);
Debug.Assert(destination != null);
Debug.Assert(!bytesRemaining.HasValue || bytesRemaining.Value >= 0);
Debug.Assert(buffer != null);

while (true)
{
// The natural end of the range.
if (bytesRemaining.HasValue && bytesRemaining.Value <= 0)
{
return;
}

cancel.ThrowIfCancellationRequested();

int readLength = buffer.Length;
if (bytesRemaining.HasValue)
{
readLength = (int)Math.Min(bytesRemaining.Value, (long)readLength);
}
int count = await source.ReadAsync(buffer, 0, readLength, cancel);

if (bytesRemaining.HasValue)
{
bytesRemaining -= count;
}

// End of the source stream.
if (count == 0)
{
return;
}

cancel.ThrowIfCancellationRequested();

await destination.WriteAsync(buffer, 0, count, cancel);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. See License.txt in the project root for license information.

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Http.Features;
Expand All @@ -12,20 +13,10 @@ namespace Microsoft.AspNet.Http.Extensions.Tests
public class SendFileResponseExtensionsTests
{
[Fact]
public void SendFileSupport()
{
var context = new DefaultHttpContext();
var response = context.Response;
Assert.False(response.SupportsSendFile());
context.Features.Set<IHttpSendFileFeature>(new FakeSendFileFeature());
Assert.True(response.SupportsSendFile());
}

[Fact]
public Task SendFileWhenNotSupported()
public Task SendFileWhenFileNotFoundThrows()
{
var response = new DefaultHttpContext().Response;
return Assert.ThrowsAsync<NotSupportedException>(() => response.SendFileAsync("foo"));
return Assert.ThrowsAsync<FileNotFoundException>(() => response.SendFileAsync("foo"));
}

[Fact]
Expand Down