2007年11月5日月曜日

Http Requestを自前で処理する

.NETアプリでGoogleReader からfeed情報を取り込む場合、Googleのサーバーにリクエストするurl中にfeedアドレスが含まれます。
例えば以下の様なアドレスでGET要求を送信します
http://www.google.com/reader/atom/feed/http://blog.atelierlune.com/atom.xml?xt=user/-/state/com.google/read

これが、.NETのHttpWebRequestを使って処理すると、内部処理でリクエストアドレスに含まれる"//"が"/"となってしまいます。コレは相対アドレスを絶対アドレスに変換しようとしてるのかも知れませんが、Googleのサーバへの要求としては不適切となってしまいます

この問題がどうしても解決する方法がわからないので、Http Requestを自前で処理するようにしてみました。


なんだか無駄な事をしている様に思います
本来ならサーバーからxmlデータを取得するだけなら

XmlDocument.Load(url);

を使用するだけで済みます。
今回はGoogleへのユーザー認証済みのCookie付きでアクセスするために、HttpWebRequestを使ってきましたが、更にHttp Requestまで自前で書くことに....

とりあえず目的のFeed情報は取得できるようになったけど、
自前で記述したHttpRequestはSSL通信は無理だろうなぁ...


とりあえず試験的に書いたソース。どっかの解説ページそのまんま+Chunked Encodingの解除
TcpClientクラスを使うともう少しスッキリ書けるのかな?
public String HttpGet(String url,String param)
{
url = url.Substring(url.IndexOf("://")+3);
String server = url.Substring(0,url.IndexOf("/"));
url = url.Substring(server.Length);
IPAddress hostaddr = Dns.GetHostByName(server).AddressList[0];
IPEndPoint hostendp = new IPEndPoint(hostaddr,80);

String reqMsg = "GET " + url + " HTTP/1.1\r\n"
+ "HOST: " + server + "\r\n"
+ "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.8.1.9) Gecko/20071025 Firefox/2.0.0.9\r\n"
+ "Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\n"
+ "Accept-Language: ja,en-us;q=0.7,en;q=0.3\r\n"
+ "Accept-Charset: utf-8;q=0.7,*;q=0.7\r\n";
if (param != null)
reqMsg += param + "\r\n";
reqMsg += "Connection: Close\r\n\r\n";

byte []reqBytes = Encoding.UTF8.GetBytes(reqMsg);

System.Net.Sockets.Socket sock = new System.Net.Sockets.Socket(
System.Net.Sockets.AddressFamily.InterNetwork,
System.Net.Sockets.SocketType.Stream,
System.Net.Sockets.ProtocolType.Tcp);
sock.Connect(hostendp);
sock.Send(reqBytes,reqBytes.Length,System.Net.Sockets.SocketFlags.None);

byte [] resByte=new byte[1024];
System.IO.MemoryStream mem = new MemoryStream();

// 受信
while (true)
{
int rsize = sock.Receive(resByte, resByte.Length, System.Net.Sockets.SocketFlags.None);
if (rsize == 0)
break;
mem.Write(resByte, 0, rsize);
}
mem.Seek(0, SeekOrigin.Begin);

// ヘッダの解釈
String [] resHdr = ReadLine(mem).Split(' ');
if (!resHdr[0].StartsWith("HTTP/")
|| !resHdr[1].StartsWith("200"))
{
throw new ArgumentException("Http Response Error");
}

// ヘッダの終わりまで読み飛ばす
bool Chunked=false;
while(true)
{
String hdr = ReadLine(mem);
if(hdr.StartsWith("Transfer-Encoding:"))
{
if(hdr.Substring("Transfer-Encoding:".Length).Trim().ToLower() =="chunked")
Chunked=true;
}
else if(hdr=="")
break;
}

// 本文を取得する(Chunkedの場合)
string resMsg="";
if(Chunked)
{
while (true)
{
String lens = ReadLine(mem).TrimEnd(';');
int len = 0;
if (lens != "")
len = Int32.Parse(lens, System.Globalization.NumberStyles.AllowHexSpecifier);
if (len == 0)
break;
resMsg += Encoding.UTF8.GetString(mem.GetBuffer(), (int)mem.Position, len);
mem.Seek(len, SeekOrigin.Current);
// 改行を捨てる
ReadLine(mem);
}
}
else
{
resMsg = Encoding.UTF8.GetString(mem.GetBuffer(), (int)mem.Position, (int)mem.Length);
}
mem.Close();

//閉じる
sock.Shutdown(System.Net.Sockets.SocketShutdown.Both);
sock.Close();

return resMsg;

}


2 件のコメント:

ori さんのコメント...

おもいっきりはずしている可能性が高いですが,
http://www.google.com/reader/atom/feed/http%3A%2F%2Fblog.atelierlune.c
みたいにURLエンコードしても駄目ですか?

おとーぽん さんのコメント...

oriさんコメントありがとうございます

ためしに
Uri uri = new Uri("http://www.google.com/reader/atom/feed/http%3A%2F%2Fblog.atelierlune");

ってやってみると、
http://www.google.com/reader/atom/feed/http%3A/blog.atelierlune

となったので、だめみたいです...

今はTcpClientを使ってプログラムを書き直しているのですが、なぜか送信文字列にゴミが入ってしまって通信エラーになってしまいます
なかなか思うようには行かないです