/// <summary> /// Sets up the socket reader to read the Content of the HTTP request /// </summary> private void SetUpReadingContent() { ContentLength = long.Parse(WebConnection.Headers["CONTENT-LENGTH"]); if (ContentLength <= WebServer.MaxInMemoryContentSize) Content = new AsyncWebConnectionContent.InMemory(ContentLength); else if (ContentLength <= WebServer.MaxContentSize) Content = new AsyncWebConnectionContent.OnDisk(); else { Close(WebResults.FromString(Status._413_Request_Entity_Too_Large, "Too much data, max size: " + WebServer.MaxContentSize.ToString())); return; } // TODO: get rid of this /*if (BufferBytesRead > 4) if (Encoding.UTF8.GetString(Buffer).StartsWith("POST")) System.Diagnostics.Debugger.Break();*/ // Give the reader the additional read bytes if (BufferBytesRead >= ContentLength) { byte[] toCopy = new byte[ContentLength]; Array.Copy(Buffer, 0, toCopy, 0, toCopy.Length); Content.TakeBytes(toCopy); if (BufferBytesRead == ContentLength) BufferBytesRead = 0; else { byte[] oldBuffer = Buffer; Buffer = new byte[oldBuffer.Length]; Array.Copy(oldBuffer, ContentLength, Buffer, 0, BufferBytesRead - ContentLength); BufferBytesRead = Convert.ToInt32(Convert.ToInt64(BufferBytesRead) - ContentLength); } DoIO = NoOp; WebConnectionIOState = WebConnectionIOState.PerformingRequest; PerformRequest(); } else if (0 == BufferBytesRead) { ReadStartTime = DateTime.UtcNow; WebConnectionIOState = WebConnectionIOState.ReadingContent; DoIO = ReadContent; //StartReadingSocket(); } else // Buffer has less bytes then what's needed { byte[] toCopy = new byte[BufferBytesRead]; Array.Copy(Buffer, 0, toCopy, 0, BufferBytesRead); Content.TakeBytes(toCopy); BufferBytesRead = 0; ReadStartTime = DateTime.UtcNow; WebConnectionIOState = WebConnectionIOState.ReadingContent; DoIO = ReadContent; //StartReadingSocket(); } }
/// <summary> /// Handles when the results are sent. This even works with non-blocking long-polling! /// </summary> void WebConnection_ResultsSent() { WebConnection.ResultsSent -= new GenericVoid(WebConnection_ResultsSent); if (null != Content) { Content.Dispose(); Content = null; } if (!WebServer.KeepAlive) Close(); else { /*if (BufferBytesRead > 0) { // If there is a complete header in the buffer, use it before switching to the ReadingHeader mode HeaderEnd = Array<byte>.IndexOf( Buffer, HeaderEndMarker, 0, BufferBytesRead); if (-1 != HeaderEnd) { WebConnectionIOState = WebConnectionIOState.ParsingHeader; DoIO = NoOp; HandleHeader(); return; } }*/ WebConnectionIOState = WebConnectionIOState.ReadingHeader; DoIO = ReadHeader; //StartReadingSocket(); } }
/// <summary> /// Called when the header is completely loaded /// </summary> /// <param name="socketReader"></param> private void PerformRequest() { WebConnectionIOState = WebConnectionIOState.PerformingRequest; DoIO = NoOp; WebConnection.ResultsSent += new GenericVoid(WebConnection_ResultsSent); //WebConnection.HandleConnection(Content); ThreadPool.QueueUserWorkItem(delegate(object wcc) { WebConnection.HandleConnection((IWebConnectionContent)wcc); }, Content); /*Thread subThread = new Thread(delegate() { WebConnection.HandleConnection(Content); Thread.Sleep(TimeSpan.FromMinutes(5)); }); subThread.Start();*/ }
/// <summary> /// Callback for when there's incoming data on the socket /// </summary> /// <param name="ar"></param> private void ReadCallback(IAsyncResult ar) { #if DEBUG //ThreadIds.Add(Thread.CurrentThread.ManagedThreadId); #endif if (!Socket.Connected) { WebConnectionIOState = WebConnectionIOState.Disconnected; return; } try { ReadingSocket = false; int read = Socket.EndReceive(ar); if (read > 0) { //log.Trace("Contents recieved:\n" + Encoding.UTF8.GetString(Buffer, BufferBytesRead, read)); BufferBytesRead = BufferBytesRead + read; /*if (Encoding.UTF8.GetString(Buffer).StartsWith("POST /TestLargeTextFile")) if (System.Diagnostics.Debugger.IsAttached) System.Diagnostics.Debugger.Break();*/ #if DEBUG DoIOName = DoIO.Method.Name; Read = read; #endif DoIO(); } //if (BufferBytesRead < Buffer.Length) while (Buffer.Length == BufferBytesRead) Thread.Sleep(5); StartReadingSocket(); } catch (Exception e) { log.Error("Error when reading from a socket", e); // Attempt to gracefully close the socket try { Close(WebResults.FromString(Status._500_Internal_Server_Error, "An unhandled error occured")); } catch { } } }
/// <summary> /// Instructs the object to read a bit of the header /// </summary> /// <returns></returns> public void ReadHeader() { HeaderEnd = Array<byte>.IndexOf( Buffer, HeaderEndMarker, 0, BufferBytesRead); // If the header end is found, then stop reading the socket and start handling the connection if (-1 != HeaderEnd) { WebConnectionIOState = WebConnectionIOState.ParsingHeader; DoIO = NoOp; HandleHeader(); } else if (BufferBytesRead == WebServer.HeaderSize) // The header is too long Close(WebResults.FromString(Status._414_Request_URI_Too_Long, "Max header length: " + WebServer.HeaderSize.ToString())); else if (ReadStartTime + WebServer.HeaderTimeout < DateTime.UtcNow) // The sending header timed out Close(WebResults.FromString(Status._408_Request_Timeout, "Headers time out after: " + WebServer.HeaderTimeout.ToString())); //else //StartReadingSocket(); }
/// <summary> /// Closes the socket reader, for all intents and purposes /// </summary> private void Close() { Socket.Close(); WebConnectionIOState = WebConnectionIOState.Disconnected; DoIO = NoOp; }
/// <summary> /// Sets up the socket reader to read the Content of the HTTP request /// </summary> private void SetUpReadingContent() { ContentLength = long.Parse(WebConnection.Headers["CONTENT-LENGTH"]); if (ContentLength <= WebServer.MaxInMemoryContentSize) Content = new WebConnectionContent.InMemory(ContentLength); else if (ContentLength <= WebServer.MaxContentSize) Content = new WebConnectionContent.OnDisk(); else { Close(WebResults.From(Status._413_Request_Entity_Too_Large, "Too much data, max size: " + WebServer.MaxContentSize.ToString())); return; } if (BufferBytesRead >= ContentLength) { byte[] toCopy = new byte[ContentLength]; Array.Copy(Buffer, 0, toCopy, 0, toCopy.Length); Content.TakeBytes(toCopy); if (BufferBytesRead == ContentLength) BufferBytesRead = 0; else { byte[] oldBuffer = Buffer; Buffer = new byte[oldBuffer.Length]; Array.Copy(oldBuffer, ContentLength, Buffer, 0, BufferBytesRead - ContentLength); //System.Buffer.BlockCopy(Buffer, Convert.ToInt32(ContentLength), Buffer, 0, Convert.ToInt32(BufferBytesRead - ContentLength)); BufferBytesRead = Convert.ToInt32(Convert.ToInt64(BufferBytesRead) - ContentLength); } WebConnectionIOState = WebConnectionIOState.PerformingRequest; PerformRequest(); return; } else if (0 == BufferBytesRead) { ReadStartTime = DateTime.UtcNow; WebConnectionIOState = WebConnectionIOState.ReadingContent; } else // Buffer has less bytes then what's needed { byte[] toCopy = new byte[BufferBytesRead]; Array.Copy(Buffer, 0, toCopy, 0, BufferBytesRead); Content.TakeBytes(toCopy); BufferBytesRead = 0; ReadStartTime = DateTime.UtcNow; WebConnectionIOState = WebConnectionIOState.ReadingContent; } ReadContent(); }
/// <summary> /// Asyncronous read for starting a new request /// </summary> /// <param name="result"></param> private void StartNewConnection(IAsyncResult result) { log.Trace("Incoming request on keepalive socket"); try { BufferBytesRead += Socket.EndReceive(result); } catch { } WebConnectionIOState = WebConnectionIOState.ReadingHeader; try { Start(); } // If there's an exception, it means the socket is disconnected. catch { WebConnectionIOState = WebConnectionIOState.Disconnected; } }
/// <summary> /// Sends the stream to the browser. This should be called on the same thread that owns this web connection /// </summary> /// <param name="stream"></param> private void SendToBrowserInt(Stream stream) { byte[] buffer = new byte[60000]; DateTime sendEnd = DateTime.UtcNow.AddMinutes(5); using (stream) try { if (Socket.Connected) { int bytesRead; do { bytesRead = stream.Read(buffer, 0, buffer.Length); int bytesSent = 0; if (bytesRead > 0) do { if (DateTime.UtcNow > sendEnd) { log.Warn("Sending timed out"); Close(); return; } bytesSent += Socket.Send(buffer, bytesSent, bytesRead - bytesSent, SocketFlags.None); } while (bytesSent < bytesRead); } while (bytesRead > 0); } else { log.Debug("Connection Dropped...."); return; } if (KeepAlive) { ReadStartTime = DateTime.UtcNow; //WebConnectionIOState = WebConnectionIOState.ReadingHeader; //Start(); // I currently syspect that going into Async mode between requests is causing weird delays and slowness // If data is available, immediately start reading the header, else, end the thread and use a callback to restart it if (Socket.Available > 0 || BufferBytesRead > 0) { WebConnectionIOState = WebConnectionIOState.ReadingHeader; // Another thread is used because this method needs to be non-blocking Start(); } else { WebConnectionIOState = WebConnectionIOState.Idle; // Set the timeout to the total header timeout Socket.ReceiveTimeout = Convert.ToInt32(WebServer.HeaderTimeout.TotalMilliseconds); try { Socket.BeginReceive( Buffer, 0, Buffer.Length, SocketFlags.None, StartNewConnection, null); } // If there's an exception, it means the socket is disconnected. catch { WebConnectionIOState = WebConnectionIOState.Disconnected; } } } else Close(); } catch (Exception e) { log.Error("Error Occurred", e); Close(); } finally { stream.Close(); if (null != Content) { Content.Dispose(); Content = null; } } }
/// <summary> /// Call for when the web connection is reading content /// </summary> /// <returns></returns> private void ReadContent() { while (true) { int bytesRead = Socket.Receive(Buffer, BufferBytesRead, Buffer.Length - BufferBytesRead, SocketFlags.None); if (0 == bytesRead) { WebConnectionIOState = WebConnectionIOState.Disconnected; Close(); return; } BufferBytesRead += bytesRead; if (Content.BytesRead + BufferBytesRead <= ContentLength) { byte[] localBuffer = new byte[BufferBytesRead]; Array.Copy(Buffer, localBuffer, BufferBytesRead); BufferBytesRead = 0; Content.TakeBytes(localBuffer); } else { // Additional data needs to stay on the buffer byte[] localBuffer = new byte[ContentLength - Content.BytesRead]; Array.Copy(Buffer, localBuffer, localBuffer.Length); byte[] oldBuffer = Buffer; Buffer = new byte[oldBuffer.Length]; Array.Copy(oldBuffer, localBuffer.Length, Buffer, 0, BufferBytesRead - localBuffer.Length); //System.Buffer.BlockCopy(Buffer, localBuffer.Length, Buffer, 0, BufferBytesRead - localBuffer.Length); BufferBytesRead = BufferBytesRead - localBuffer.Length; Content.TakeBytes(localBuffer); } // If the header end is found, then stop reading the socket and start handling the connection if (Content.BytesRead >= ContentLength) { WebConnectionIOState = WebConnectionIOState.PerformingRequest; PerformRequest(); return; } else if (ReadStartTime + WebServer.ContentTimeout < DateTime.UtcNow) { // The sending header timed out Content.Dispose(); WebConnectionIOState = WebConnectionIOState.Disconnected; WebConnection.SendResults(WebResults.From(Status._408_Request_Timeout, "Content times out after: " + WebServer.HeaderTimeout.ToString())); Socket.Close(); return; } } }
/// <summary> /// Closes the socket reader, for all intents and purposes /// </summary> internal void Close() { try { Socket.Shutdown(SocketShutdown.Both); Socket.Close(); } catch { } WebConnectionIOState = WebConnectionIOState.Disconnected; WebServer.WebServerTerminating -= new EventHandler<EventArgs>(WebServer_WebServerTerminating); if (null != Closed) Closed(this, new EventArgs()); }
/// <summary> /// Instructs the object to read a bit of the header /// </summary> /// <returns></returns> public void ReadHeader() { #if DEBUG int headerHandled = 0; #endif while (true) { if (BufferBytesRead > HeaderEndMarker.Length) { int headerEnd = Array<byte>.IndexOf(Buffer, HeaderEndMarker); if (headerEnd != -1) { WebConnectionIOState = WebConnectionIOState.ParsingHeader; #if DEBUG headerHandled++; #endif HandleHeader(headerEnd); return; } } if (BufferBytesRead >= WebServer.HeaderSize) { // The header is too long Close(WebResults.From(Status._414_Request_URI_Too_Long, "Max header length: " + WebServer.HeaderSize.ToString())); return; } else if (ReadStartTime + WebServer.HeaderTimeout < DateTime.UtcNow) { // The sending header timed out Close(WebResults.From(Status._408_Request_Timeout, "Headers time out after: " + WebServer.HeaderTimeout.ToString())); return; } // If the header is incomplete, read more of it int bytesRead = Socket.Receive(Buffer, BufferBytesRead, Buffer.Length - BufferBytesRead, SocketFlags.None); if (0 == bytesRead) { WebConnectionIOState = WebConnectionIOState.Disconnected; Close(); return; } BufferBytesRead += bytesRead; } }
/// <summary> /// Overall driver for watching the socket. This is started on its own thread /// </summary> public void MonitorSocket() { try { // Timeout if the client takes a really long time to send bytes Socket.ReceiveTimeout = Convert.ToInt32(TimeSpan.FromSeconds(15).TotalMilliseconds); WebConnection = new WebConnection(WebServer, Socket.RemoteEndPoint, SendToBrowser); ReadHeader(); } catch (ThreadAbortException) { Close(); } // Exceptions that occur when a socket is closed are just swallowed; this keeps the logs clean catch (ObjectDisposedException) { WebConnectionIOState = WebConnectionIOState.Disconnected; Close(); } catch (SocketException)// se) { //log.InfoFormat("Error when performing IO for a connection from {0}", se, RemoteEndPoint); WebConnectionIOState = WebConnectionIOState.Disconnected; Close(); } catch (Exception e) { log.ErrorFormat("Fatal error when performing IO for a connection from {0}", e, RemoteEndPoint); WebConnectionIOState = WebConnectionIOState.Disconnected; Close(); } }