/// <summary> /// Provides an implementation of <see cref="IHostBufferPolicySelector"/> suited for use /// in an ASP.NET environment which provides direct support for input and output buffering. /// </summary> public class WebHostBufferPolicySelector : IHostBufferPolicySelector { ....../// <summary> /// Determines whether the host should buffer the <see cref="HttpResponseMessage"/> entity body. /// </summary> /// <param name="response">The <see cref="HttpResponseMessage"/>response for which to determine /// whether host output buffering should be used for the response entity body.</param> /// <returns><c>true</c> if buffering should be used; otherwise a streamed response should be used.</returns> public virtual bool UseBufferedOutputStream(HttpResponseMessage response) { if (response == null) { throw Error.ArgumentNull("response"); }
// Any HttpContent that knows its length is presumably already buffered internally. HttpContent content = response.Content; if (content != null) { long? contentLength = content.Headers.ContentLength; if (contentLength.HasValue && contentLength.Value >= 0) { return false; }
// Content length is null or -1 (meaning not known). // Buffer any HttpContent except StreamContent and PushStreamContent return !(content is StreamContent || content is PushStreamContent); }
return false; } }
从上述如下一句可以很明显的知道:
return !(content is StreamContent || content is PushStreamContent);
/// <summary> /// Provides an <see cref="HttpContent"/> implementation that exposes an output <see cref="Stream"/> /// which can be written to directly. The ability to push data to the output stream differs from the /// <see cref="StreamContent"/> where data is pulled and not pushed. /// </summary> public class PushStreamContent : HttpContent { private readonly Func<Stream, HttpContent, TransportContext, Task> \_onStreamAvailable;
/// <summary> /// Initializes a new instance of the <see cref="PushStreamContent"/> class. The /// <paramref name="onStreamAvailable"/> action is called when an output stream /// has become available allowing the action to write to it directly. When the /// stream is closed, it will signal to the content that is has completed and the /// HTTP request or response will be completed. /// </summary> /// <param name="onStreamAvailable">The action to call when an output stream is available.</param> public PushStreamContent(Action<Stream, HttpContent, TransportContext> onStreamAvailable) : this(Taskify(onStreamAvailable), (MediaTypeHeaderValue)null) { } ...... }
var client = new HttpClient(); var content = new PushStreamContent((stream, httpContent, transportContext) => { var serializer = new JsonSerializer(); using (var writer = new StreamWriter(stream)) { serializer.Serialize(writer, data); } }); var response = await client.PostAsync(uri, content);
public class FileProvider : IFileProvider { private readonly string \_filesDirectory; private const string AppSettingsKey = "DownloadDir";
public FileProvider() { var fileLocation = ConfigurationManager.AppSettings\[AppSettingsKey\]; if (!String.IsNullOrWhiteSpace(fileLocation)) { \_filesDirectory \= fileLocation; } }
public class PartialContentFileStream : Stream { private readonly long \_start; private readonly long \_end; private long \_position; private FileStream \_fileStream; public PartialContentFileStream(FileStream fileStream, long start, long end) { \_start \= start; \_position \= start; \_end \= end; \_fileStream \= fileStream;
if (start > 0) { \_fileStream.Seek(start, SeekOrigin.Begin); } }
/// <summary> /// 依据偏离位置读取 /// </summary> /// <param name="buffer"></param> /// <param name="offset"></param> /// <param name="count"></param> /// <returns></returns> public override int Read(byte\[\] buffer, int offset, int count) { int byteCountToRead = count; if (\_position + count > \_end) { byteCountToRead \= (int)(\_end - \_position) + 1; } var result = \_fileStream.Read(buffer, offset, byteCountToRead); \_position += byteCountToRead; return result; }
public override IAsyncResult BeginRead(byte\[\] buffer, int offset, int count, AsyncCallback callback, object state) { int byteCountToRead = count; if (\_position + count > \_end) { byteCountToRead \= (int)(\_end - \_position); } var result = \_fileStream.BeginRead(buffer, offset, count, (s) \=> { \_position += byteCountToRead; callback(s); }, state); return result; } ...... }
更新上述下载的完整逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
public HttpResponseMessage GetFile(string fileName) { fileName \= "HBuilder.windows.5.2.6.zip"; if (!FileProvider.Exists(fileName)) { throw new HttpResponseException(HttpStatusCode.NotFound); } long fileLength = FileProvider.GetLength(fileName); var fileInfo = GetFileInfoFromRequest(this.Request, fileLength);
var stream = new PartialContentFileStream(FileProvider.Open(fileName), fileInfo.From, fileInfo.To); var response = new HttpResponseMessage(); response.Content \= new StreamContent(stream, BufferSize); SetResponseHeaders(response, fileInfo, fileLength, fileName); return response; }