Last active
August 24, 2020 14:22
-
-
Save vendettamit/5774087 to your computer and use it in GitHub Desktop.
WebApi controller to handle the single partial request for content
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Configuration; | |
using System.IO; | |
using System.Linq; | |
using System.Net; | |
using System.Net.Http; | |
using System.Net.Http.Headers; | |
using System.Reflection; | |
using System.Web; | |
using System.Web.Http; | |
namespace WebFileHost.Controllers | |
{ | |
/// <summary> | |
/// TODO: Implement caching to save disk IO and speed up the content serving | |
/// Contains the methods to handle the download requests of packages | |
/// </summary> | |
public class DownloadController : ApiController | |
{ | |
/// <summary> | |
/// Default mime type for response | |
/// </summary> | |
private const string MimeType = "application/octet-stream"; | |
/// <summary> | |
/// Package directory path defined in the configuration | |
/// </summary> | |
private const string AppsettingPackageDirectoryPath = "FilesDirectoryPath"; | |
/// <summary> | |
/// Log file path defined in appsetting in configuration | |
/// </summary> | |
private const string AppsettingLogFilePath = "LogFilePath"; | |
/// <summary> | |
/// Default directory path for requested files | |
/// </summary> | |
private readonly string packageDirectoryFilePath; | |
/// <summary> | |
/// Log file path | |
/// </summary> | |
private readonly string logFilePath; | |
/// <summary> | |
/// Initializes a new instance of the <see cref="DownloadController" /> class. | |
/// </summary> | |
public DownloadController() | |
{ | |
if (!string.IsNullOrEmpty(this.GetAppsettingValue(AppsettingLogFilePath))) | |
{ | |
this.logFilePath = this.GetAppsettingValue(AppsettingLogFilePath); | |
} | |
else | |
{ | |
this.logFilePath = "C:\\temp\\RequestResponseHeadersLog.log"; | |
} | |
if (!string.IsNullOrEmpty(this.GetAppsettingValue(AppsettingPackageDirectoryPath))) | |
{ | |
this.packageDirectoryFilePath = this.GetAppsettingValue(AppsettingPackageDirectoryPath); | |
} | |
else | |
{ | |
this.packageDirectoryFilePath = @"D:\files"; | |
} | |
} | |
/// <summary> | |
/// Gets the file from server. | |
/// </summary> | |
/// <param name="fileName">Name of the file.</param> | |
/// <returns>response content of file</returns> | |
public HttpResponseMessage GetFile(string fileName) | |
{ | |
this.LogRequestHttpHeaders(this.logFilePath, Request); | |
HttpResponseMessage result = null; | |
var fullFilePath = Path.Combine(this.packageDirectoryFilePath, fileName); | |
if (Request.Headers.Range == null || Request.Headers.Range.Ranges.Count == 0 || Request.Headers.Range.Ranges.FirstOrDefault().From.Value == 0) | |
{ | |
// Get the complete file | |
FileStream sourceStream = File.Open(fullFilePath, FileMode.Open, FileAccess.Read, FileShare.Read); | |
BufferedStream bs = new BufferedStream(sourceStream); | |
result = new HttpResponseMessage(HttpStatusCode.OK); | |
result.Content = new StreamContent(bs); | |
result.Content.Headers.ContentType = new MediaTypeHeaderValue(MimeType); | |
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") | |
{ | |
FileName = fileName | |
}; | |
} | |
else | |
{ | |
// Get the partial part | |
var item = Request.Headers.Range.Ranges.FirstOrDefault(); | |
if (item != null && item.From.HasValue) | |
{ | |
result = this.GetPartialContent(fileName, item.From.Value); | |
} | |
} | |
this.LogResponseHttpHeaders(this.logFilePath, result); | |
return result; | |
} | |
/// <summary> | |
/// Gets the file content from the given start byte range to end. | |
/// </summary> | |
/// <param name="fileName">Name of the file.</param> | |
/// <param name="startByteRange">The start byte range.</param> | |
/// <returns>response content of the file</returns> | |
public HttpResponseMessage GetFile(string fileName, long startByteRange) | |
{ | |
return this.GetPartialContent(fileName, startByteRange); | |
} | |
/// <summary> | |
/// Reads the partial content of physical file. | |
/// </summary> | |
/// <param name="fileName">Name of the file.</param> | |
/// <param name="partial">The partial.</param> | |
/// <returns>response content of the file</returns> | |
private HttpResponseMessage GetPartialContent(string fileName, long partial) | |
{ | |
var fullFilePath = Path.Combine(this.packageDirectoryFilePath, fileName); | |
FileInfo fileInfo = new FileInfo(fullFilePath); | |
long startByte = partial; | |
Action<Stream, HttpContent, TransportContext> pushContentAction = (outputStream, content, context) => | |
{ | |
try | |
{ | |
var buffer = new byte[65536]; | |
using (var file = File.Open(fullFilePath, FileMode.Open, FileAccess.Read, FileShare.Read)) | |
{ | |
var bytesRead = 1; | |
file.Seek(startByte, SeekOrigin.Begin); | |
int length = Convert.ToInt32((fileInfo.Length - 1) - startByte) + 1; | |
while (length > 0 && bytesRead > 0) | |
{ | |
bytesRead = file.Read(buffer, 0, Math.Min(length, buffer.Length)); | |
outputStream.Write(buffer, 0, bytesRead); | |
length -= bytesRead; | |
} | |
} | |
} | |
catch (HttpException ex) | |
{ | |
this.LogException(ex); | |
} | |
finally | |
{ | |
outputStream.Close(); | |
} | |
}; | |
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.PartialContent); | |
result.Content = new PushStreamContent(pushContentAction, new MediaTypeHeaderValue(MimeType)); | |
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") | |
{ | |
FileName = fileName | |
}; | |
return result; | |
} | |
/// <summary> | |
/// Logs the request HTTP headers. | |
/// </summary> | |
/// <param name="logFile">The log file.</param> | |
/// <param name="request">The request.</param> | |
private void LogRequestHttpHeaders(string logFile, HttpRequestMessage request) | |
{ | |
string output = string.Concat("REQUEST INFORMATION (", request.Method, ")", Environment.NewLine); | |
foreach (PropertyInfo info in request.Headers.GetType().GetProperties()) | |
{ | |
var name = info.Name; | |
var value = info.GetValue(request.Headers); | |
output += string.Format("{0}: {1}{2}", name, value, Environment.NewLine); | |
} | |
output += Environment.NewLine + Environment.NewLine; | |
File.AppendAllText(logFile, output); | |
} | |
/// <summary> | |
/// Logs the response HTTP headers. | |
/// </summary> | |
/// <param name="logFile">The log file.</param> | |
/// <param name="response">The response.</param> | |
private void LogResponseHttpHeaders(string logFile, HttpResponseMessage response) | |
{ | |
string output = string.Concat("RESPONSE INFORMATION (", response.StatusCode.ToString("d"), ")", Environment.NewLine); | |
foreach (PropertyInfo info in response.Headers.GetType().GetProperties()) | |
{ | |
var name = info.Name; | |
var value = info.GetValue(response.Headers); | |
if (value == null) | |
{ | |
continue; | |
} | |
output += string.Format("{0}: {1}{2}", name, value, Environment.NewLine); | |
} | |
output += Environment.NewLine + Environment.NewLine; | |
File.AppendAllText(logFile, output); | |
} | |
/// <summary> | |
/// Logs the exception. | |
/// </summary> | |
/// <param name="exception">The exception.</param> | |
private void LogException(Exception exception) | |
{ | |
string output = string.Format("=======> Error while processing previous request. {0}{1}", Environment.NewLine, exception.ToString()); | |
File.AppendAllText(this.logFilePath, output); | |
} | |
/// <summary> | |
/// Gets the appsetting value. | |
/// </summary> | |
/// <param name="key">The key.</param> | |
/// <returns>the appsetting value</returns> | |
private string GetAppsettingValue(string key) | |
{ | |
if (ConfigurationManager.AppSettings[key] != null) | |
{ | |
return ConfigurationManager.AppSettings[key]; | |
} | |
return string.Empty; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment