HttpWebRequestで取得したデータをcharsetに応じてデコードする (C#)(F#)
先にも書いた通り、HttpWebRequestで取得したデータをstringに変換する際、HttpWebResponsのCharacterSetプロパティで示されるエンコード形式は正確でない事があります。
下記のサンプルでは、HttpWebRequestで取得したデータをbyteで受け、一旦ASCIIでstringに変換しヘッダの中にcharsetの指定が有るかどうかを検索しています。
検索の結果、エンコードの形式が判明した場合は、byteから改めて適切なエンコードでstringへ変換します。
C#サンプル
using System; using System.Net; using System.IO; using System.Text; namespace Examlpe { class Program { static void Main(string[] args) { //HTTPリクエスト var req = HttpWebRequest.Create("http://www.sapporoworks.ne.jp/spw/"); req.Timeout = 15000;//タイムアウト(15秒) //HTPレスポンス var res = (HttpWebResponse)req.GetResponse(); //byte [] で読み込む var buf = ReadBytes(res.GetResponseStream()); //デフォルトをASCIIとする var encoding = Encoding.ASCII; //ASCIIでstringに変換する string str =encoding.GetString(buf); //1行ごとMETAヘッダにcharesetの指定がないかを検索する foreach (var s in str.Split('n')) { var l = s.ToUpper(); if (l.IndexOf("<META") != -1 && l.IndexOf("CHARSET")!=-1) { //指定があった場合は、エンコードの種類を取得する var charset = GetCharset(l); encoding = Encoding.GetEncoding(charset); //取得したエンコードでstringへの変換をやり直す str = encoding.GetString(buf); break; } } Console.WriteLine(str); Console.WriteLine(); Console.WriteLine("何かのキーを押してください。"); Console.ReadKey(); } //charset=の後ろを取得する static string GetCharset(string str) { //"="の検索開始 int start = str.IndexOf("CHARSET"); if (start == -1) return "";//失敗 for(int i=start;;i++){ if(str[i]=='='){ start = i+1; break; } } int end = 0; for (int i = start + 1; ; i++) { if(str[i]=='"' || str[i]=='>' || str[i]==' ' || str[i]==';'){ end=i; break; } } if (end == 0) return "";//失敗 return str.Substring(start,end-start); } //Streamからbyte[]で読み込む static byte[] ReadBytes(Stream stream) { var buf = new byte[0]; var tmp = new byte[1024]; while (true) { var len = stream.Read(tmp, 0, 1024); if (len <= 0) break; buf = AppendBuffer(buf, tmp , len); } return buf; } //byte[]への追加 static byte[] AppendBuffer(byte[] buf, byte[] tmp ,int len) { var res = new byte[buf.Length + len]; Buffer.BlockCopy(buf, 0, res, 0, buf.Length); Buffer.BlockCopy(tmp, 0, res, buf.Length, len); return res; } } }
F#サンプル
もっと、シンプルに書けないものだろうか・・・・
open System open System.Net open System.IO open System.Text open System.Linq ////charset=の後ろのエンコードの種類を取得する let GetCharset (str:string) = //charsetの後の最初の'='の位置をスタート位置(s)として取得 let s = str.IndexOf('=',str.IndexOf("CHARSET")) + 1 //スタート位置以降で、最初に'"'、'>'、' '若しくは';'が現れた位置を終了位置(e)として取得する let e = [s..str.Length-1] |>Seq.tryFind(fun n -> str.[n] = '"' || str.[n] = '>'|| str.[n] = ' '|| str.[n] = ';') if e <> None then str.Substring(s,e.Value-s) //スタート位置〜終了位置の部分文字列を取得 else "" //失敗 //Streamからbyte[]で読み込む let rec ReadBytes (stream:Stream,buf) = let tmp = Array.create 1024 0uy match stream.Read(tmp, 0, 1024) with |n when n>0 -> ReadBytes ( stream , Array.append buf tmp.[0..n-1] ) |_ -> buf //byte[] をstringに変換する let rec GetString (encoding:Encoding,buf:byte[]) = let str = encoding.GetString(buf) //ASCIIでデコードした場合は、METAヘッダを確認する if encoding = Encoding.ASCII then let s = //<charsetのある行を取得する str.Split('n') |> Seq.map(fun s->s.ToUpper()) |> Seq.tryFind(fun s->s.IndexOf("<META") <> -1 && s.IndexOf("CHARSET") <> -1) match s with |None -> str |_ -> match GetCharset(s.Value) with //エンコードの種類を取得 |charset when charset <> "" -> GetString(Encoding.GetEncoding(charset),buf) |_ -> str else str //HTTPリクエスト let req = HttpWebRequest.Create("http://www.sapporoworks.ne.jp/spw/") req.Timeout <- 15000//タイムアウト(15秒) //HTPレスポンス let res = req.GetResponse():?>HttpWebResponse //Streamからbyte[]で読み込む let buf = ReadBytes(res.GetResponseStream(),[||]) //byte[] をstringに変換する let str = GetString(Encoding.ASCII,buf) printfn "%s" str printfn "" printfn "何かのキーを押してください。" Console.ReadKey() |> ignore
2011.11.24追記
mclh46氏による記事
http://d.hatena.ne.jp/mclh46/20111123/1322015736