HTTP のリクエストを解析する

HTTP のリクエストを解析し、プログラム上で扱いやすくするクラス。

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.IO;

namespace ReadTest
{
	class Request
	{
		public string Method { get; private set; }
		public string Uri { get; private set; }
		public string Version { get; private set; }
		public NameValueCollection Headers { get; private set; }

		public Request()
		{
			Method = "GET";
			Uri = "/";
			Version = "HTTP/1.1";
			Headers = new NameValueCollection();
		}

		public static Request Parse(string raw)
		{
			Request req = new Request();
			using (StringReader reader = new StringReader(raw))
			{
				string requestLine = reader.ReadLine();
				string[] args = requestLine.Split(' ');
				req.Method = args[0];
				req.Uri = args[1];
				req.Version = args[2];

				string line = reader.ReadLine();
				string name = null; // 複数行ヘッダのための記憶
				while (!string.IsNullOrEmpty(line))
				{
					int index = line.IndexOf(':');
					if (index >= 0)
					{
						name = line.Substring(0, index); // ':' より前
						string value = line.Substring(index + 1).Trim(); // ':' よりあと
						req.AddHeader(name, value);
					}
					else if (line[0] == ' ' || line[0] == '\t') // 行の先頭が SP または TAB なら前の行の続き
					{
						if (name != null)
							req.AddHeader(name, line.Trim()); // ',' でつないでしまう。
						// 複数行ヘッダの場合、前の行と SP でつないだ方がいいかも。
					}

					line = reader.ReadLine(); // next line
				}
			}
			return req;
		}

		public void AddHeader(string name, string value)
		{
			Headers.Add(name, value); // 既に name があったらコンマでつなぐ

			switch (name)
			{
				case "Content-Length":
					// this.ContentLength = int.Parse(value); とか。
					break;
				default:
					break;
			}
		}

		public override string ToString()
		{
			StringBuilder sb = new StringBuilder();
			sb.AppendFormat("{0} {1} {2}\r\n", Method, Uri, Version);
			for (int i = 0; i < Headers.Count; i++)
			{
				string name = Headers.GetKey(i);
				string value = Headers.Get(i);
				sb.AppendFormat("{0}: {1}\r\n", name, value);
			}
			sb.AppendLine(); // End Of Headers
			return sb.ToString();
		}
	}
}

とこんな感じ。例外処理はしてないです。常に正常なヘッダであると仮定しています。TryParse とかあるといいかも。ヘッダより後、つまりボディは無視してる。Request.Body とかに入れても良いけど、巨大ファイルだったら困るので。あとは適切なプロパティを実装してさらに扱いやすくすればいいかな。
ちなみにほとんどこのまま HTTP レスポンスの解析にも使えます。Method を Version に、Uri を StatusCode に、Version を ReasonPhrase に変えれば良いです。
んでテストコード。

static void Main(string[] args)
{
	Debug.Listeners.Add(new ConsoleTraceListener());

	string request = @"GET /http.html HTTP/1.1
Host: localhost
User-Agent: hoge
Referrer: http://localhost/
Content-Length: 3
User-Agent: nullpo
 gaxtsu
	hello

ABC";

	Request req = Request.Parse(request);

	Debug.WriteLine(req.ToString());
}

ボディの ABC に意味はないです。そこまで読まれませんので。
一応結果も張っておきます

GET /http.html HTTP/1.1
Host: localhost
User-Agent: hoge,nullpo,gaxtsu,hello
Referrer: http://localhost/
Content-Length: 3