ストリームから1行読み取りたいなら、StreamReader なんかが使えるわけだけど、文字列としてしか扱えないので不便だ。HTTP 通信ではヘッダまでは ASCII で読んで良いが、それ以降は画像だったり書庫だったりのデータが送られてくる事があるので、StreamReader では対応できない。生データのまま ReadLine できるクラスが必要なので作ってみた。
名前はてきとうに HttpHandler とかにしておく。
class HttpHandler { Stream _stream; public HttpHandler(Stream stream) { _stream = stream; } public int Read(byte[] buffer, int offset, int length) { return _stream.Read(buffer, offset, length); } public string ReadLine() { byte[] line = ReadTo(new byte[] { 13, 10 }); return Encoding.ASCII.GetString(line); } public byte[] ReadTo(byte[] terminator) { using (MemoryStream buffer = new MemoryStream()) { int count = 0; int ch; while ((ch = _stream.ReadByte()) != -1) { buffer.WriteByte((byte)ch); if (ch == terminator[count]) { if (count == (terminator.Length - 1)) break; count++; } else { count = 0; } } return buffer.ToArray(); } } }
んで実行コード
private static void Run() { TcpClient client = new TcpClient("www.google.co.jp", 80); BufferedStream stream = new BufferedStream(client.GetStream()); // リクエスト送信 byte[] request = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\nHost: www.google.co.jp\r\nConnection: close\r\n\r\n"); stream.Write(request, 0, request.Length); stream.Flush(); HttpHandler handler = new HttpHandler(stream); byte[] headers = handler.ReadTo(new byte[] { 13, 10, 13, 10 }); // ヘッダの終わりまで読む Console.WriteLine(Encoding.ASCII.GetString(headers)); // ボディを読む。Transfer-Encoding: chunked が前提。 MemoryStream body = new MemoryStream(); string line = handler.ReadLine(); int chunkSize = int.Parse(line, System.Globalization.NumberStyles.HexNumber); byte[] receive = new byte[chunkSize]; while (chunkSize > 0) { Console.WriteLine(chunkSize); int remain = chunkSize; int offset = 0; while (remain > 0) { int read = handler.Read(receive, offset, remain); offset += read; remain -= read; } body.Write(receive, 0, receive.Length); handler.ReadLine(); // Skip CRLF line = handler.ReadLine(); // next chunk size chunkSize = int.Parse(line, System.Globalization.NumberStyles.HexNumber); Array.Resize<byte>(ref receive, chunkSize); } line = handler.ReadLine(); // trailer while (!string.IsNullOrEmpty(line.Trim())) { Console.WriteLine(line); line = handler.ReadLine(); } Console.WriteLine(Encoding.Default.GetString(body.ToArray())); body.Close(); stream.Close(); client.Close(); }
HttpHandler.ReadTo で指定した byte[] にマッチするまで読めます。
ReadByte() は遅いかもしれない。まぁヘッダ読むくらいにしか使ってないので気になりませんが。一応 BufferedStream を使って読んだ方がいいとおもう。