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

Commit 80c7f1b

Browse files
committed
Change SendFileAsync to use a fallback implementation instead of throwing
- If the user wants to use the SendFile API directly then they can access the feature explicitly. #496
1 parent a0a1c38 commit 80c7f1b

File tree

6 files changed

+114
-174
lines changed

6 files changed

+114
-174
lines changed

src/Microsoft.AspNet.Http.Abstractions/project.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"System.Net.Primitives": "4.0.11-*",
3737
"System.Net.WebSockets": "4.0.0-*",
3838
"System.Reflection.TypeExtensions": "4.0.1-*",
39+
"System.IO": "4.0.11-*",
3940
"System.Runtime": "4.0.21-*",
4041
"System.Runtime.InteropServices": "4.0.21-*",
4142
"System.Security.Claims": "4.0.1-*",

src/Microsoft.AspNet.Http.Extensions/Properties/Resources.Designer.cs

Lines changed: 0 additions & 46 deletions
This file was deleted.

src/Microsoft.AspNet.Http.Extensions/Resources.resx

Lines changed: 0 additions & 123 deletions
This file was deleted.

src/Microsoft.AspNet.Http.Extensions/SendFileResponseExtensions.cs

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.IO;
56
using System.Threading;
67
using System.Threading.Tasks;
7-
using Microsoft.AspNet.Http.Extensions;
88
using Microsoft.AspNet.Http.Features;
99

1010
namespace Microsoft.AspNet.Http
@@ -33,7 +33,7 @@ public static bool SupportsSendFile(this HttpResponse response)
3333
/// Sends the given file using the SendFile extension.
3434
/// </summary>
3535
/// <param name="response"></param>
36-
/// <param name="fileName"></param>
36+
/// <param name="fileName">The full or relative path to the file.</param>
3737
/// <returns></returns>
3838
public static Task SendFileAsync(this HttpResponse response, string fileName)
3939
{
@@ -74,10 +74,56 @@ public static Task SendFileAsync(this HttpResponse response, string fileName, lo
7474
var sendFile = response.HttpContext.Features.Get<IHttpSendFileFeature>();
7575
if (sendFile == null)
7676
{
77-
throw new NotSupportedException(Resources.Exception_SendFileNotSupported);
77+
return SendFileAsync(response.Body, fileName, offset, count, cancellationToken);
7878
}
7979

8080
return sendFile.SendFileAsync(fileName, offset, count, cancellationToken);
8181
}
82+
83+
// Not safe for overlapped writes.
84+
private static async Task SendFileAsync(Stream outputStream, string fileName, long offset, long? length, CancellationToken cancel)
85+
{
86+
cancel.ThrowIfCancellationRequested();
87+
88+
if (string.IsNullOrWhiteSpace(fileName))
89+
{
90+
throw new ArgumentNullException(nameof(fileName));
91+
}
92+
if (!File.Exists(fileName))
93+
{
94+
throw new FileNotFoundException(string.Empty, fileName);
95+
}
96+
97+
var fileInfo = new FileInfo(fileName);
98+
if (offset < 0 || offset > fileInfo.Length)
99+
{
100+
throw new ArgumentOutOfRangeException(nameof(offset), offset, string.Empty);
101+
}
102+
103+
if (length.HasValue &&
104+
(length.Value < 0 || length.Value > fileInfo.Length - offset))
105+
{
106+
throw new ArgumentOutOfRangeException(nameof(length), length, string.Empty);
107+
}
108+
109+
var fileStream = new FileStream(
110+
fileName,
111+
FileMode.Open,
112+
FileAccess.Read,
113+
FileShare.ReadWrite,
114+
bufferSize: 1024 * 64,
115+
options: FileOptions.Asynchronous | FileOptions.SequentialScan);
116+
117+
try
118+
{
119+
fileStream.Seek(offset, SeekOrigin.Begin);
120+
121+
await StreamCopyOperation.CopyToAsync(fileStream, outputStream, length, cancel);
122+
}
123+
finally
124+
{
125+
fileStream.Dispose();
126+
}
127+
}
82128
}
83129
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using System.IO;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
10+
namespace Microsoft.AspNet.Http
11+
{
12+
// FYI: In most cases the source will be a FileStream and the destination will be to the network.
13+
internal static class StreamCopyOperation
14+
{
15+
private const int DefaultBufferSize = 1024 * 16;
16+
17+
internal static async Task CopyToAsync(Stream source, Stream destination, long? length, CancellationToken cancel)
18+
{
19+
long? bytesRemaining = length;
20+
byte[] buffer = new byte[DefaultBufferSize];
21+
22+
Debug.Assert(source != null);
23+
Debug.Assert(destination != null);
24+
Debug.Assert(!bytesRemaining.HasValue || bytesRemaining.Value >= 0);
25+
Debug.Assert(buffer != null);
26+
27+
while (true)
28+
{
29+
// The natural end of the range.
30+
if (bytesRemaining.HasValue && bytesRemaining.Value <= 0)
31+
{
32+
return;
33+
}
34+
35+
cancel.ThrowIfCancellationRequested();
36+
37+
int readLength = buffer.Length;
38+
if (bytesRemaining.HasValue)
39+
{
40+
readLength = (int)Math.Min(bytesRemaining.Value, (long)readLength);
41+
}
42+
int count = await source.ReadAsync(buffer, 0, readLength, cancel);
43+
44+
if (bytesRemaining.HasValue)
45+
{
46+
bytesRemaining -= count;
47+
}
48+
49+
// End of the source stream.
50+
if (count == 0)
51+
{
52+
return;
53+
}
54+
55+
cancel.ThrowIfCancellationRequested();
56+
57+
await destination.WriteAsync(buffer, 0, count, cancel);
58+
}
59+
}
60+
}
61+
}

test/Microsoft.AspNet.Http.Extensions.Tests/SendFileResponseExtensionsTests.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved. See License.txt in the project root for license information.
22

33
using System;
4+
using System.IO;
45
using System.Threading;
56
using System.Threading.Tasks;
67
using Microsoft.AspNet.Http.Features;
@@ -22,10 +23,10 @@ public void SendFileSupport()
2223
}
2324

2425
[Fact]
25-
public Task SendFileWhenNotSupported()
26+
public Task SendFileWhenFileNotFoundThrows()
2627
{
2728
var response = new DefaultHttpContext().Response;
28-
return Assert.ThrowsAsync<NotSupportedException>(() => response.SendFileAsync("foo"));
29+
return Assert.ThrowsAsync<FileNotFoundException>(() => response.SendFileAsync("foo"));
2930
}
3031

3132
[Fact]

0 commit comments

Comments
 (0)