Skip to content

Instantly share code, notes, and snippets.

@vendettamit
Last active August 24, 2020 14:22
Show Gist options
  • Save vendettamit/5774087 to your computer and use it in GitHub Desktop.
Save vendettamit/5774087 to your computer and use it in GitHub Desktop.
WebApi controller to handle the single partial request for content
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