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

Commit

Permalink
Change SendFileAsync to use a fallback implementation instead of thro…
Browse files Browse the repository at this point in the history
…wing

- If the user wants to use the SendFile API directly then they can access the feature
explicitly.

#496
  • Loading branch information
davidfowl committed Dec 3, 2015
1 parent a0a1c38 commit 80c7f1b
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 174 deletions.
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.

52 changes: 49 additions & 3 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 Down Expand Up @@ -33,7 +33,7 @@ public static bool SupportsSendFile(this HttpResponse response)
/// Sends the given file using the SendFile extension.
/// </summary>
/// <param name="response"></param>
/// <param name="fileName"></param>
/// <param name="fileName">The full or relative path to the file.</param>
/// <returns></returns>
public static Task SendFileAsync(this HttpResponse response, string fileName)
{
Expand Down Expand Up @@ -74,10 +74,56 @@ 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);
}

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();

if (string.IsNullOrWhiteSpace(fileName))
{
throw new ArgumentNullException(nameof(fileName));
}
if (!File.Exists(fileName))
{
throw new FileNotFoundException(string.Empty, fileName);
}

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);
}

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

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

await StreamCopyOperation.CopyToAsync(fileStream, outputStream, length, cancel);
}
finally
{
fileStream.Dispose();
}
}
}
}
61 changes: 61 additions & 0 deletions src/Microsoft.AspNet.Http.Extensions/StreamCopyOperation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// 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
{
private const int DefaultBufferSize = 1024 * 16;

internal static async Task CopyToAsync(Stream source, Stream destination, long? length, CancellationToken cancel)
{
long? bytesRemaining = length;
byte[] buffer = new byte[DefaultBufferSize];

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 @@ -22,10 +23,10 @@ public void SendFileSupport()
}

[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

0 comments on commit 80c7f1b

Please sign in to comment.