EmptyHttpResponseHandler.cs
/*
* All intellectual rights of this framework, including this source file belong to Appicacy, René Vaessen. * Customers of Appicacy, may copy and change it, as long as this header remains. * */ using System.Text; using Ninject; using GenXdev.MemoryManagement; using GenXdev.Configuration; using GenXdev.Buffers; using GenXdev.AsyncSockets.Arguments; using GenXdev.Helpers; using System.Net; namespace GenXdev.AsyncSockets.Handlers { public class EmptyHttpResponseHandler : SocketHandlerBase, IHttpResponseHandler { #region Initialization [Inject] public EmptyHttpResponseHandler( IKernel Kernel, IMemoryManagerConfiguration MemoryConfiguration, IServiceMemoryManager MemoryManager, String ServiceName #if (Logging) , IServiceLogger Logger #endif , object Pool = null ) : base(Kernel, MemoryConfiguration, MemoryManager, ServiceName #if (Logging) , Logger #endif , Pool ) { } protected override void SetupHandlers() { this.OnHandleInitialize += EmptyHttpResponseHandler_OnHandleInitialize; this.OnHandleReceive += EmptyHttpResponseHandler_OnHandleReceive; this.OnHandleSend += EmptyHttpResponseHandler_OnHandleSend; this.OnHandleAsyncAction += EmptyHttpResponseHandler_OnHandleAsyncAction; this.OnHandleReset += EmptyHttpResponseHandler_OnHandleReset; } #endregion #region Fields int BytesToReceiveFromClient; bool CloseConnection; bool HeadersOnly; string externalIP; // 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[] HttpContentTypeJsonHeader = (new UTF8Encoding(false, true)).GetBytes("Content-Type: application/json; charset=utf-8\r\n"); 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[] HttpConnectionCloseHeader = (new UTF8Encoding(false, true)).GetBytes("Connection: close\r\n"); static byte[] HttpContentLengthHeaderOpening = (new UTF8Encoding(false, true)).GetBytes("Content-Length: "); static byte[] HttpContentLength0Header = (new UTF8Encoding(false, true)).GetBytes("Content-Length: 0\r\n"); static byte[] HttpHeaderClosing = (new UTF8Encoding(false, true)).GetBytes("\r\n"); #endregion #region Properties public IHttpRequestSocketHandler HttpRequestSocketHandler { get; set; } public int HttpResultCode { get; set; } public string HttpStatusMessage { get; set; } public string Location { get; set; } public bool AddJsonTlsHostInfo { get; set; } public bool ForceDisconnect { get; set; } #endregion #region Handling void EmptyHttpResponseHandler_OnHandleInitialize(object sender, HandleSocketBEventArgs e) { #region ---------------------------------------------------------------------------------------------[LOG]- #if (Logging) Logger.LogHandlerFlowMessage(saeaHandler, () => { return "Sending \"" + HttpResultCode.ToString() + " " + HttpStatusMessage + "\""; }); #endif #endregion ------------------------------------------------------------------------------------------------ BytesToReceiveFromClient = 0; if (HttpRequestSocketHandler.HttpRequestHeaders.IsValidRequest && (HttpRequestSocketHandler.HttpRequestHeaders != null) && (HttpRequestSocketHandler.HttpRequestHeaders.Method == "POST")) { // get content length if (!Int32.TryParse(HttpRequestSocketHandler.HttpRequestHeaders["content-length"], out BytesToReceiveFromClient)) { ForceDisconnect = true; BytesToReceiveFromClient = 0; } } ForceDisconnect |= !String.IsNullOrWhiteSpace(Location); // set timeout SetActivityTimestamp(false); SetCurrentStageTimeoutSeconds(10, !ForceDisconnect); if ((HttpRequestSocketHandler.HttpRequestHeaders == null) || ForceDisconnect) { CloseConnection = true; } else { var connection = HttpRequestSocketHandler.HttpRequestHeaders.IsValidRequest ? (String)HttpRequestSocketHandler.HttpRequestHeaders["connection"] : "close"; if ((!String.IsNullOrWhiteSpace(connection)) && (connection.ToLower() == "close")) { CloseConnection = true; } if (HttpRequestSocketHandler.HttpRequestHeaders.IsValidRequest) // check http method switch (HttpRequestSocketHandler.HttpRequestHeaders.Method) { case "HEAD": case "OPTIONS": HeadersOnly = true; break; default: HeadersOnly = false; break; } } e.NextAction = NextRequestedHandlerAction.AsyncAction; } void DetermineFirstAction(HandleSocketBEventArgs e) { var left = Math.Max(0, BytesToReceiveFromClient - e.RxBuffer.Count); var tooMuch = (left > 1024 * 1024); if (left > 0 && !tooMuch) { SetCurrentStageTimeoutSeconds(5, !tooMuch); e.Count = BytesToReceiveFromClient - e.RxBuffer.Count; e.NextAction = NextRequestedHandlerAction.Receive; return; } // initialize response InitializeHttpResponseHeaders(e.TxBuffer); e.NextAction = NextRequestedHandlerAction.Send; } void EmptyHttpResponseHandler_OnHandleReceive(object sender, HandleSocketBEventArgs e) { var left = Math.Max(0, BytesToReceiveFromClient - e.RxBuffer.Count); if (left > 0) { e.Count = left; e.NextAction = NextRequestedHandlerAction.Receive; return; } // initialize response InitializeHttpResponseHeaders(e.TxBuffer); e.NextAction = NextRequestedHandlerAction.Send; } void EmptyHttpResponseHandler_OnHandleSend(object sender, HandleSocketBEventArgs e) { // still sending headers? if (e.TxBuffer.Count > 0) { e.NextAction = NextRequestedHandlerAction.Send; return; } // all done! if (ForceDisconnect) { e.NextAction = NextRequestedHandlerAction.Disconnect; return; } // restore and continue http handler e.NextAction = NextRequestedHandlerAction.ReleaseControl; } async Task EmptyHttpResponseHandler_OnHandleAsyncAction(object sender, HandleSocketBEventArgs e) { externalIP = GenXdev.Helpers.Network.GetPublicExternalHostname(((IPEndPoint)saeaHandler.AcceptSocket.RemoteEndPoint).ToString()); DetermineFirstAction(e); } void EmptyHttpResponseHandler_OnHandleReset(object sender, EventArgs e) { // dereference HttpRequestSocketHandler = null; HttpResultCode = 200; HttpStatusMessage = "OK"; Location = null; ForceDisconnect = false; BytesToReceiveFromClient = 0; AddJsonTlsHostInfo = false; CloseConnection = false; } #endregion #region Private #region Response void InitializeHttpResponseHeaders(DynamicBuffer txBuffer) { // write http version and result code txBuffer.Add(HttpHeaderOpening); txBuffer.Add(UTF8Encoding.GetBytes(HttpResultCode.ToString() + " " + HttpStatusMessage)); 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); } } } var isWebSocket = (HttpRequestSocketHandler.HttpRequestHeaders != null) && (HttpRequestSocketHandler.HttpRequestHeaders["upgrade"].ToLower() == "websocket"); var haveLocation = !String.IsNullOrWhiteSpace(Location); if (!HeadersOnly && haveLocation && AddJsonTlsHostInfo && !isWebSocket) { // lookup hostname to suggest var hostName = String.Empty; hostName = GenXdev.Helpers.Pki.GetPrimaryDnsName( // match what is expected on the TLS certificate HttpRequestSocketHandler.TLSConfiguration.TLS_CertificateUsage == Configuration.SocketHandlerTLSCertificateUsage.AutoGenerate ? externalIP : (HttpRequestSocketHandler.TLSConfiguration.TLS_AdditionalHostNames != null) && (HttpRequestSocketHandler.TLSConfiguration.TLS_AdditionalHostNames.Length > 0) ? HttpRequestSocketHandler.TLSConfiguration.TLS_AdditionalHostNames[0] : externalIP , HttpRequestSocketHandler.TLSConfiguration.TLS_AdditionalHostNames ); // change location's hostname string hostUri = HttpRequestSocketHandler.HttpRequestHeaders["host"]; if (!string.IsNullOrWhiteSpace(hostUri)) hostUri = hostUri.Split(new char[] { ':' })[0].Trim().ToLowerInvariant(); var newUri = new UriBuilder(Location); newUri.Host = GenXdev.Helpers.Pki.GetPrimaryDnsName(hostUri, HttpRequestSocketHandler.TLSConfiguration.TLS_AdditionalHostNames); newUri.Scheme = "https"; Location = newUri.ToString(); // construct json var uri = new Uri(Location); var json = UTF8Encoding.GetBytes(GenXdev.Helpers.Serialization.ToJsonAnonymous( new { x_redirect = new { host = hostName, port = uri.Port, url = isWebSocket ? Location.Replace("http", "ws") : Location } } )); // write content type txBuffer.Add(HttpContentTypeJsonHeader); // write content length txBuffer.Add(HttpContentLengthHeaderOpening); txBuffer.Add(UTF8Encoding.GetBytes(json.Length.ToString())); txBuffer.Add(HttpHeaderClosing); // need to disconnect afterwards? if (CloseConnection) { txBuffer.Add(HttpConnectionCloseHeader); } // end headers section txBuffer.Add(HttpHeaderClosing); txBuffer.Add(json); } else { if (haveLocation) { txBuffer.Add((new UTF8Encoding(false, true)).GetBytes("Location: " + (isWebSocket ? Location.Replace("http", "ws") : Location) + "\r\n")); } if (!CloseConnection) { // write content length txBuffer.Add(HttpContentLength0Header); // need to disconnect afterwards? if (CloseConnection) { txBuffer.Add(HttpConnectionCloseHeader); } } txBuffer.Add(HttpHeaderClosing); } } #endregion #endregion } } |