/// <summary>Parses incoming data into an HTTP request</summary> /// <param name="buffer">Buffer containing the received data</param> /// <param name="receivedByteCount">Number of bytes in the receive buffer</param> private void parseRequest(byte[] buffer, int receivedByteCount) { ArraySegment<byte> data = new ArraySegment<byte>(buffer, 0, receivedByteCount); while(data.Count > 0) { Response response; // Response that will be delivered to the client bool dropConnection = false; // Whether to close the connection try { // Try to parse a complete request from the bytes the client has sent to us. // If there isn't enough data available yet, we exit here and hopefully the // next time data arrives it will be enough to complete the request entity. Request request = this.parser.ProcessBytes(data.Array, data.Offset, data.Count); if(request == null) { break; } // We've got an actual request. Now let's handle it using the implementation // provided by the deriving class from the user! response = ProcessRequest(request); } catch(Exceptions.HttpException httpException) { response = new Response(httpException.StatusCode, httpException.Message); dropConnection = true; } catch(Exception exception) { response = new Response(StatusCode.S500_Internal_Server_Error, exception.Message); dropConnection = true; } // Transform the response entity into a stream of bytes and send it back to // the client. If any additional data has to be transmitted, this will be handled // by the attachment transmitter. byte[] responseBytes = ResponseFormatter.Format(response); this.socket.Send(responseBytes); // TODO: Can get an ObjectDisposedException here! // TODO: Evil hack! if(response.AttachedStream != null) { this.socket.Send(((MemoryStream)response.AttachedStream).GetBuffer()); } // TODO: Respect the keep-alive request header here // If keep-alive is not set, we should close the connection here! if(dropConnection) { Drop(); } else { // Now take the remaining data out of the parser and bring the parser back into // its initial state so it can begin parsing the next request data = this.parser.GetRemainingData(); this.parser.Reset(); } } }
/// <summary>Processes the provided request and generates a server response</summary> /// <param name="request">Request to be processed by the server</param> /// <returns>The response to the server request</returns> protected virtual Response ProcessRequest(Request request) { #if true // GENERATE_DUMMY_RESPONSE Console.WriteLine( DateTime.Now.ToString() + " Processed request for " + request.Uri ); // Here's the HTML document we want to send to the client MemoryStream messageMemory = new MemoryStream(); StreamWriter writer = new StreamWriter(messageMemory, Encoding.UTF8); writer.WriteLine("<html>"); writer.WriteLine("<head><title>Hello World</title></head>"); writer.WriteLine("<body><small>Hello World from the Nuclex Web Server</small></body>"); writer.WriteLine("</html>"); writer.Flush(); // Prepare the response message Response theResponse = new Response(StatusCode.S200_OK); // Add some random headers web server's like to provider theResponse.Headers.Add("Cache-Control", "private"); theResponse.Headers.Add("Content-Type", "text/html; charset=UTF-8"); theResponse.Headers.Add("Date", "Wed, 30 Jul 2008 14:01:06 GMT"); theResponse.Headers.Add("Server", "Nuclex"); // Attach the HTML document to our response theResponse.AttachStream(messageMemory); // Now comes the important part, specify how many bytes we're going to // transmit. // TODO: This should be done by parseRequest() // Whether it's needed or not depends on the transport protocol used messageMemory.Position = 0; theResponse.Headers.Add("Content-Length", messageMemory.Length.ToString()); #endif return theResponse; }
/// <summary> /// Convert the provided response into the HTTP response transport format /// </summary> /// <param name="response">Response that will be converted</param> /// <returns> /// An array of bytes containing the response in the HTTP response format /// </returns> public static byte[] Format(Response response) { // Make sure the status code is in a valid numerical range. We will also need // this int later to avoid .NETs enum to string conversion helpfulness int intStatusCode = (int)response.StatusCode; if((intStatusCode < 100) || (intStatusCode > 599)) { throw new InvalidOperationException("Invalid status code in response"); } // Build a usable status message string. If the request processor specified // null for the status message, we try to replace it with the default message // for the given status code. If that also doesn't work, we'll send an empty // status message. string statusMessage = response.StatusMessage; if(statusMessage == null) { statusMessage = StatusCodeHelper.GetDefaultDescription(response.StatusCode); if(statusMessage == null) { statusMessage = string.Empty; } } // Calculate the total size of the return packet int combinedLength; int versionLength, statusMessageLength; int[,] headerLengths; // Status line { // Sum up the length of the constant parts of the reply // <Version> SP:1 <StatusCode:3> SP:1 <StatusMessage> CRLF:2 <Headers> CRLF:2 combinedLength = 1 + 3 + 1 + 2 + 2; // SP + StatusCode + SP + CRLF + CRLF // Add the length of the HTTP version and status message versionLength = iso88591Encoding.GetByteCount(response.Version); combinedLength += versionLength; statusMessageLength = iso88591Encoding.GetByteCount(statusMessage); combinedLength += statusMessageLength; } // Headers { // Add the constant per-header overhead of 4 bytes // <FieldName> DCOLON:1 SP:1 <FieldValue> CRLF:2 combinedLength += response.Headers.Count * 4; headerLengths = new int[response.Headers.Count, 2]; // Now sum up the length of the dynamic parts in all header fields int headerIndex = 0; foreach(KeyValuePair<string, string> header in response.Headers) { headerLengths[headerIndex, 0] = iso88591Encoding.GetByteCount(header.Key); headerLengths[headerIndex, 1] = iso88591Encoding.GetByteCount(header.Value); combinedLength += headerLengths[headerIndex, 0] + headerLengths[headerIndex, 1]; ++headerIndex; } } // Now that we know the length of the response message, we can set up a buffer and // construct the response in it. byte[] responseBytes = new byte[combinedLength]; int responseByteIndex = 0; // Write the HTTP protocol version iso88591Encoding.GetBytes( response.Version, 0, response.Version.Length, responseBytes, 0 ); responseByteIndex += versionLength; responseBytes[responseByteIndex] = SP; ++responseByteIndex; //IFormatProvider // Write the status code iso88591Encoding.GetBytes( intStatusCode.ToString(), 0, 3, responseBytes, responseByteIndex ); // TODO: use explicit locale responseByteIndex += 3; responseBytes[responseByteIndex] = SP; ++responseByteIndex; // Write the status message iso88591Encoding.GetBytes( statusMessage, 0, statusMessage.Length, responseBytes, responseByteIndex ); responseByteIndex += statusMessageLength; responseBytes[responseByteIndex] = CR; ++responseByteIndex; responseBytes[responseByteIndex] = LF; ++responseByteIndex; // Write headers { int headerIndex = 0; foreach(KeyValuePair<string, string> header in response.Headers) { // Write header name iso88591Encoding.GetBytes( header.Key, 0, header.Key.Length, responseBytes, responseByteIndex ); responseByteIndex += headerLengths[headerIndex, 0]; responseBytes[responseByteIndex] = DC; ++responseByteIndex; responseBytes[responseByteIndex] = SP; // not in RFC, but common practice ++responseByteIndex; // Write header value iso88591Encoding.GetBytes( header.Value, 0, header.Value.Length, responseBytes, responseByteIndex ); responseByteIndex += headerLengths[headerIndex, 1]; responseBytes[responseByteIndex] = CR; ++responseByteIndex; responseBytes[responseByteIndex] = LF; // not in RFC, but common practice ++responseByteIndex; ++headerIndex; } } responseBytes[responseByteIndex] = CR; ++responseByteIndex; responseBytes[responseByteIndex] = LF; // not in RFC, but common practice ++responseByteIndex; return responseBytes; }