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

Commit 5b175be

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. - Removed SupportsSendFile - Don't check for existence, let FileStream throw - Updated Doc comments - Pass the buffer into StreamCopyOperation - Using a real using instead of try finally.
1 parent a0a1c38 commit 5b175be

File tree

6 files changed

+103
-200
lines changed

6 files changed

+103
-200
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: 42 additions & 19 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
@@ -14,26 +14,11 @@ namespace Microsoft.AspNet.Http
1414
/// </summary>
1515
public static class SendFileResponseExtensions
1616
{
17-
/// <summary>
18-
/// Checks if the SendFile extension is supported.
19-
/// </summary>
20-
/// <param name="response"></param>
21-
/// <returns>True if sendfile feature exists in the response.</returns>
22-
public static bool SupportsSendFile(this HttpResponse response)
23-
{
24-
if (response == null)
25-
{
26-
throw new ArgumentNullException(nameof(response));
27-
}
28-
29-
return response.HttpContext.Features.Get<IHttpSendFileFeature>() != null;
30-
}
31-
3217
/// <summary>
3318
/// Sends the given file using the SendFile extension.
3419
/// </summary>
3520
/// <param name="response"></param>
36-
/// <param name="fileName"></param>
21+
/// <param name="fileName">The full to the file.</param>
3722
/// <returns></returns>
3823
public static Task SendFileAsync(this HttpResponse response, string fileName)
3924
{
@@ -54,7 +39,7 @@ public static Task SendFileAsync(this HttpResponse response, string fileName)
5439
/// Sends the given file using the SendFile extension.
5540
/// </summary>
5641
/// <param name="response"></param>
57-
/// <param name="fileName">The full or relative path to the file.</param>
42+
/// <param name="fileName">The full to the file.</param>
5843
/// <param name="offset">The offset in the file.</param>
5944
/// <param name="count">The number of types to send, or null to send the remainder of the file.</param>
6045
/// <param name="cancellationToken"></param>
@@ -74,10 +59,48 @@ public static Task SendFileAsync(this HttpResponse response, string fileName, lo
7459
var sendFile = response.HttpContext.Features.Get<IHttpSendFileFeature>();
7560
if (sendFile == null)
7661
{
77-
throw new NotSupportedException(Resources.Exception_SendFileNotSupported);
62+
return SendFileAsync(response.Body, fileName, offset, count, cancellationToken);
7863
}
7964

8065
return sendFile.SendFileAsync(fileName, offset, count, cancellationToken);
8166
}
67+
68+
// Not safe for overlapped writes.
69+
private static async Task SendFileAsync(Stream outputStream, string fileName, long offset, long? length, CancellationToken cancel)
70+
{
71+
cancel.ThrowIfCancellationRequested();
72+
73+
var fileInfo = new FileInfo(fileName);
74+
if (offset < 0 || offset > fileInfo.Length)
75+
{
76+
throw new ArgumentOutOfRangeException(nameof(offset), offset, string.Empty);
77+
}
78+
79+
if (length.HasValue &&
80+
(length.Value < 0 || length.Value > fileInfo.Length - offset))
81+
{
82+
throw new ArgumentOutOfRangeException(nameof(length), length, string.Empty);
83+
}
84+
85+
int bufferSize = 1024 * 16;
86+
87+
var fileStream = new FileStream(
88+
fileName,
89+
FileMode.Open,
90+
FileAccess.Read,
91+
FileShare.ReadWrite,
92+
bufferSize: bufferSize,
93+
options: FileOptions.Asynchronous | FileOptions.SequentialScan);
94+
95+
using (fileStream)
96+
{
97+
fileStream.Seek(offset, SeekOrigin.Begin);
98+
99+
// TODO: Use buffer pool
100+
var buffer = new byte[bufferSize];
101+
102+
await StreamCopyOperation.CopyToAsync(fileStream, buffer, outputStream, length, cancel);
103+
}
104+
}
82105
}
83106
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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+
internal static async Task CopyToAsync(Stream source, byte[] buffer, Stream destination, long? length, CancellationToken cancel)
16+
{
17+
long? bytesRemaining = length;
18+
Debug.Assert(source != null);
19+
Debug.Assert(destination != null);
20+
Debug.Assert(!bytesRemaining.HasValue || bytesRemaining.Value >= 0);
21+
Debug.Assert(buffer != null);
22+
23+
while (true)
24+
{
25+
// The natural end of the range.
26+
if (bytesRemaining.HasValue && bytesRemaining.Value <= 0)
27+
{
28+
return;
29+
}
30+
31+
cancel.ThrowIfCancellationRequested();
32+
33+
int readLength = buffer.Length;
34+
if (bytesRemaining.HasValue)
35+
{
36+
readLength = (int)Math.Min(bytesRemaining.Value, (long)readLength);
37+
}
38+
int count = await source.ReadAsync(buffer, 0, readLength, cancel);
39+
40+
if (bytesRemaining.HasValue)
41+
{
42+
bytesRemaining -= count;
43+
}
44+
45+
// End of the source stream.
46+
if (count == 0)
47+
{
48+
return;
49+
}
50+
51+
cancel.ThrowIfCancellationRequested();
52+
53+
await destination.WriteAsync(buffer, 0, count, cancel);
54+
}
55+
}
56+
}
57+
}

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

Lines changed: 3 additions & 12 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;
@@ -12,20 +13,10 @@ namespace Microsoft.AspNet.Http.Extensions.Tests
1213
public class SendFileResponseExtensionsTests
1314
{
1415
[Fact]
15-
public void SendFileSupport()
16-
{
17-
var context = new DefaultHttpContext();
18-
var response = context.Response;
19-
Assert.False(response.SupportsSendFile());
20-
context.Features.Set<IHttpSendFileFeature>(new FakeSendFileFeature());
21-
Assert.True(response.SupportsSendFile());
22-
}
23-
24-
[Fact]
25-
public Task SendFileWhenNotSupported()
16+
public Task SendFileWhenFileNotFoundThrows()
2617
{
2718
var response = new DefaultHttpContext().Response;
28-
return Assert.ThrowsAsync<NotSupportedException>(() => response.SendFileAsync("foo"));
19+
return Assert.ThrowsAsync<FileNotFoundException>(() => response.SendFileAsync("foo"));
2920
}
3021

3122
[Fact]

0 commit comments

Comments
 (0)