using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace Nuclex.Networking.Http {
///
/// Formats an HTTP server response container into a format the client understands
///
internal class ResponseFormatter {
/// ID of the ISO-8859-1 code page
private const int ISO_8859_1 = 28591;
/// ASCII code for the carriage return character
private const byte CR = 13;
/// ASCII code for the line feed character
private const byte LF = 10;
/// ASCII code for the space character
private const byte SP = 32;
/// ASCII code for the double colon character
private const byte DC = 58;
///
/// Convert the provided response into the HTTP response transport format
///
/// Response that will be converted
///
/// An array of bytes containing the response in the HTTP response format
///
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
// SP:1 SP:1 CRLF:2 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
// DCOLON:1 SP:1 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 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;
// Write the status code
iso88591Encoding.GetBytes(
intStatusCode.ToString(usCulture), 0, 3, responseBytes, responseByteIndex
);
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 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;
}
/// Encoding for the ISO-8859-1 codepage
///
/// Encoding used for all message headers in HTTP
///
private static readonly Encoding iso88591Encoding = Encoding.GetEncoding(ISO_8859_1);
/// CultureInfo of the en-us culture
///
/// Mainly used to convert numbers to text without requiring that the system's
/// current culture transforms into a format the client can understand.
///
private static readonly CultureInfo usCulture = new CultureInfo("en-us");
}
} // namespace Nuclex.Networking.Http