FileResourceHttpHandler.cs
using System.Text;
using Ninject; using GenXdev.MemoryManagement; using GenXdev.Configuration; using GenXdev.Buffers; using GenXdev.AsyncSockets.Arguments; namespace GenXdev.AsyncSockets.Handlers { public class FileResourceHttpHandler : SocketHandlerBase, IHttpResponseHandler { #region Initialization [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors"), Inject] public FileResourceHttpHandler( IKernel Kernel, IMemoryManagerConfiguration MemoryConfiguration, IServiceMemoryManager MemoryManager, String ServiceName #if (Logging) , IServiceLogger Logger #endif , bool KeepAlive = false , object Pool = null ) : base(Kernel, MemoryConfiguration, MemoryManager, ServiceName #if (Logging) , Logger #endif , Pool ) { this.KeepAlive = KeepAlive; } protected override void SetupHandlers() { this.OnHandleInitialize += FileResourceHttpHandler_OnHandleInitialize; this.OnHandleSend += FileResourceHttpHandler_OnHandleSend; this.OnHandleAsyncAction += FileResourceHttpHandler_OnHandleAsyncAction; this.OnHandleReset += FileResourceHttpHandler_OnHandleReset; } #endregion #region Fields bool sendingHttpError; bool KeepAlive; bool SendFileNameHeader; long BytesToSendToClient; string ContentType = ""; long ContentLength = 0; FileStream stream = null; byte[] buffer; // encodings UTF8Encoding UTF8Encoding = new UTF8Encoding(false, true); // frequently used constants static byte[] HttpHeaderOpening = (new UTF8Encoding(false, true)).GetBytes("HTTP/1.1 "); static byte[] HttpCacheControlHeader = (new UTF8Encoding(false, true)).GetBytes("Cache-Control: no-cache\r\n"); static byte[] HttpACAllowOriginHeaderOpening = (new UTF8Encoding(false, true)).GetBytes("Access-Control-Allow-Origin: "); static byte[] HttpACAllowMethodsHeader = (new UTF8Encoding(false, true)).GetBytes("Access-Control-Allow-Methods: GET, POST, OPTIONS, HEAD, PUT, CONNECT\r\n"); static byte[] HttpACMaxAgeHeader = (new UTF8Encoding(false, true)).GetBytes("Access-Control-Max-Age: 86400\r\n"); static byte[] HttpACAllowHeadersHeader = (new UTF8Encoding(false, true)).GetBytes("Access-Control-Allow-Headers: x-requested-with, content-type, accept, connection\r\n"); static byte[] HttpACAllowHeadersWithWebsocketSupportHeader = (new UTF8Encoding(false, true)).GetBytes("Access-Control-Allow-Headers: x-requested-with, content-type, accept, connection, upgrade, sec-websocket-key, sec-websocket-protocol, sec-websocket-version\r\n"); static byte[] HttpHeaderClosing = (new UTF8Encoding(false, true)).GetBytes("\r\n"); static byte[] HttpConnectionKeepAlifeHeader = (new UTF8Encoding(false, true)).GetBytes("Connection: Keep-Alive\r\n"); static byte[] HttpConnectionCloseHeader = (new UTF8Encoding(false, true)).GetBytes("Connection: Close\r\n"); #endregion #region Properties public IHttpRequestSocketHandler HttpRequestSocketHandler { get; set; } public string Location { get; set; } #endregion #region Handling void FileResourceHttpHandler_OnHandleInitialize(object sender, HandleSocketBEventArgs e) { // control returning from EmptyResponseHandler? if (sendingHttpError) { e.NextAction = NextRequestedHandlerAction.ReleaseControl; return; } #region ---------------------------------------------------------------------------------------------[LOG]- #if (Logging) Logger.LogSocketFlowMessage(saeaHandler, () => { return "got new request"; }); #endif #endregion ------------------------------------------------------------------------------------------------ SetCurrentStageTimeoutSeconds(60, true); stream = new FileStream(Location, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); buffer = new byte[MemoryConfiguration.SendBufferSize]; BytesToSendToClient = (new System.IO.FileInfo(Location)).Length; ContentLength = BytesToSendToClient; SetContentType(); #region ---------------------------------------------------------------------------------------------[LOG]- #if (Logging) Logger.LogHandlerFlowMessage(saeaHandler, () => { return " >> FILE >> " + HttpRequestSocketHandler.HttpRequestHeaders.Location.OriginalString; }); #endif #endregion ------------------------------------------------------------------------------------------------ e.NextAction = StartSendingResponse(e.TxBuffer); } void FileResourceHttpHandler_OnHandleSend(object sender, HandleSocketBEventArgs e) { e.NextAction = NextRequestedHandlerAction.AsyncAction; } async Task FileResourceHttpHandler_OnHandleAsyncAction(object sender, HandleSocketBEventArgs e) { // still sending headers? if (e.TxBuffer.Count > 0) { // continue e.NextAction = NextRequestedHandlerAction.Send; return; } else { if (HttpRequestSocketHandler.HttpRequestHeaders.Method.ToUpper() != "HEAD") { // still sending headers? if (BytesToSendToClient > 0) { // add next block var l = await stream.ReadAsync(buffer, 0, buffer.Length); e.TxBuffer.Add(buffer, 0, l); BytesToSendToClient -= l; #region ---------------------------------------------------------------------------------------------[LOG]- #if (Logging) Logger.LogSocketFlowMessage(saeaHandler, () => { return "" + BytesToSendToClient.ToString() + " bytes left"; }); #endif #endregion ------------------------------------------------------------------------------------------------ // continue e.NextAction = NextRequestedHandlerAction.Send; return; } } } // restore and continue http handler e.NextAction = NextRequestedHandlerAction.ReleaseControl; } void FileResourceHttpHandler_OnHandleReset(object sender, EventArgs e) { // reset flags sendingHttpError = false; // dereference HttpRequestSocketHandler = null; CloseStream(); } private void SetContentType() { SendFileNameHeader = false; switch (Path.GetExtension(Location).ToLower()) { case ".css": ContentType = "text/css"; break; case ".html": ContentType = "text/html"; break; case ".png": ContentType = "image/png"; break; case ".js": ContentType = "text/javascript"; break; case ".cer": SendFileNameHeader = true; ContentType = "application/x-x509-ca-cert"; break; case ".ico": ContentType = "image/icon"; break; default: SendFileNameHeader = true; ContentType = "application/octet-stream"; break; } } void CloseStream() { if (stream != null) { stream.Dispose(); stream = null; } } NextRequestedHandlerAction StartSendingResponse(DynamicBuffer TxBuffer) { #region ---------------------------------------------------------------------------------------------[LOG]- #if (Logging) Logger.LogSocketFlowMessage(saeaHandler, () => { return "Starting sending of response headers"; }); #endif #endregion ------------------------------------------------------------------------------------------------ // initialize response InitializeHttpResponseHeaders(TxBuffer); return NextRequestedHandlerAction.Send; } #endregion #region Private #region Response void InitializeHttpResponseHeaders(DynamicBuffer TxBuffer) { // write http version and result code TxBuffer.Add(HttpHeaderOpening); TxBuffer.Add(UTF8Encoding.GetBytes("200 OK")); TxBuffer.Add(HttpHeaderClosing); // set caching TxBuffer.Add(HttpCacheControlHeader); // add crossdomain security policy if requested if (HttpRequestSocketHandler.HttpRequestHeaders != null) { String origin = (String)HttpRequestSocketHandler.HttpRequestHeaders["origin"]; if (!String.IsNullOrWhiteSpace(origin)) { // allow origin TxBuffer.Add(HttpACAllowOriginHeaderOpening); TxBuffer.Add(UTF8Encoding.GetBytes(origin)); TxBuffer.Add(HttpHeaderClosing); if (HttpRequestSocketHandler.HttpRequestHeaders.Method == "OPTIONS") { // allow methods TxBuffer.Add(HttpACAllowMethodsHeader); TxBuffer.Add(HttpACAllowHeadersHeader); // max age TxBuffer.Add(HttpACMaxAgeHeader); } } } TxBuffer.Add((new UTF8Encoding(false, true)).GetBytes("Content-Type: " + ContentType + "\r\n")); if (KeepAlive) { TxBuffer.Add(HttpConnectionKeepAlifeHeader); } else { TxBuffer.Add(HttpConnectionCloseHeader); } TxBuffer.Add((new UTF8Encoding(false, true)).GetBytes("Content-Length: " + ContentLength.ToString() + "\r\n")); if (SendFileNameHeader) { TxBuffer.Add((new UTF8Encoding(false, true)).GetBytes("Content-Disposition: inline; filename=\"" + Path.GetFileName(this.Location) + "\"\r\n")); } TxBuffer.Add(HttpHeaderClosing); } #endregion #endregion #region Misc void SendHttpErrorMessage(HandleSocketBEventArgs e, int HttpResultCode, string HttpStatusMessage, bool ForceDisconnect = true) { // set flag sendingHttpError = true; // create new socket handler var newHandler = Kernel.Get<EmptyHttpResponseHandler>(); // set handler properties newHandler.HttpRequestSocketHandler = HttpRequestSocketHandler; newHandler.HttpResultCode = HttpResultCode; newHandler.HttpStatusMessage = HttpStatusMessage; newHandler.ForceDisconnect = ForceDisconnect; // delegate e.NextAction = newHandler.TakeOverConrolOfSocket(saeaHandler); } #endregion } } |