.NET Framework 4.5で新設されたHttpClientクラスを使い、Webページの内容を非常にシンプルなコードで取得する方法を解説する。
対象:.NET 4.5以降
HTTP/HTTPSプロトコルを使ってWebページを取得するには、さまざまな方法がある。これまでは、WebClientクラスやWebRequest/WebResponseクラス(いずれもSystem.Net名前空間)がよく使われてきた。本稿では、.NET Framework 4.5で新設されたHttpClientクラス(System.Net.Http名前空間)の特徴と、それを使ってWebページの内容を取得する基本的な方法を解説する。
ひと言でいえば、とても使いやすくなっている。Webページの内容を取得するコードは、オプション設定や例外処理を無視するなら1行で書けるのだ(次のコード)。
string htmlString = await (new HttpClient()).GetStringAsync("http://dev.windows.com/ja-jp");
Dim htmlString As String = Await (New HttpClient()).GetStringAsync("http://dev.windows.com/ja-jp")
それだけではなく、HttpClientクラスはREST*2にも対応している。RESTのアクセス方法に対応した、PutAsync/GetAsync/PostAsync/DeleteAsyncという四つのメソッドが用意されているのだ。
また、HttpClientクラスは、HTTPのリクエストをHttpRequestMessageオブジェクト、レスポンスをHttpResponseMessageオブジェクト(いずれもSystem.Net.Http名前空間)として扱う。これらを使うことで、例えばPOSTデータを送信するときに、簡潔にデータをセットできるようになっている。
さらに、送受信の際に別の処理を割り込ませることも可能になっている。そのような処理は、HttpMessageHandler抽象クラス(System.Net.Http名前空間)を継承したクラスを作り、そのSendAsyncメソッドをオーバーライドして実装する。そして、HttpClientクラスをインスタンス化するときにその(HttpMessageHandlerクラスを継承した)クラスのオブジェクトを渡す。この仕組みを使えば、例えばクッキーの送信*3や、Internet Explorerの設定とは異なるプロキシの利用*4や、ユーザー認証の必要なサイトへの透過的なアクセス*5などが可能になる。
前述した1行だけのコードに、ユーザーエージェント文字列の設定や例外処理などを追加して、実用的なものにしてみよう。Webページの内容を取得する部分を「GetWebPageAsync」という名前のメソッドにまとめると、次のコードのようになる。
using System;
using System.Net.Http;
using System.Threading.Tasks;
……省略……
static async Task<string> GetWebPageAsync(Uri uri)
{
using (HttpClient client = new HttpClient())
{
// ユーザーエージェント文字列をセット(オプション)
client.DefaultRequestHeaders.Add(
"User-Agent",
"Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko");
// 受け入れ言語をセット(オプション)
client.DefaultRequestHeaders.Add("Accept-Language", "ja-JP");
// タイムアウトをセット(オプション)
client.Timeout = TimeSpan.FromSeconds(10.0);
try
{
// Webページを取得するのは、事実上この1行だけ
return await client.GetStringAsync(uri);
}
catch (HttpRequestException e)
{
// 404エラーや、名前解決失敗など
Console.WriteLine("\n例外発生!");
// InnerExceptionも含めて、再帰的に例外メッセージを表示する
Exception ex = e;
while (ex != null)
{
Console.WriteLine("例外メッセージ: {0} ", ex.Message);
ex = ex.InnerException;
}
}
catch (TaskCanceledException e)
{
// タスクがキャンセルされたとき(一般的にタイムアウト)
Console.WriteLine("\nタイムアウト!");
Console.WriteLine("例外メッセージ: {0} ", e.Message);
}
return null;
}
}
Imports System.Net.Http
……省略……
Async Function GetWebPageAsync(uri As Uri) As Task(Of String)
Using client = New HttpClient()
' ユーザーエージェント文字列をセット(オプション)
client.DefaultRequestHeaders.Add(
"User-Agent",
"Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko")
' 受け入れ言語をセット(オプション)
client.DefaultRequestHeaders.Add("Accept-Language", "ja-JP")
' タイムアウトをセット(オプション)
client.Timeout = TimeSpan.FromSeconds(10.0)
Try
' Webページを取得するのは、事実上この1行だけ
Return Await client.GetStringAsync(uri)
Catch e As HttpRequestException
' 404エラーや、名前解決失敗など
Console.WriteLine(vbCr + "例外発生!")
' InnerExceptionも含めて、再帰的に例外メッセージを表示する
Dim ex As Exception = e
While (ex IsNot Nothing)
Console.WriteLine("例外メッセージ: {0} ", ex.Message)
ex = ex.InnerException
End While
Catch e As TaskCanceledException
' タスクがキャンセルされたとき(一般的にタイムアウト)
Console.WriteLine(vbCr + "タイムアウト!")
Console.WriteLine("例外メッセージ: {0} ", e.Message)
End Try
Return Nothing
End Using
End Function
コンソールプログラムで上の「GetWebPageAsync」メソッドを呼び出すMainメソッドは、次のコードのようになる。
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("HttpClientクラスでWebページを取得する");
// 時間計測用のタイマー
var timer = new System.Diagnostics.Stopwatch();
timer.Start();
// 取得したいWebページのURI
Uri webUri = new Uri("http://dev.windows.com/ja-jp");
//Uri webUri = new Uri("https://dev.windows.com/ja-jp"); // HTTPSでもOK!
//Uri webUri = new Uri("https://dev.windows.com/"); // デフォルトではリダイレクト先を取得してくれる
//Uri webUri = new Uri("https://appdev.microsoft.com/"); // 403エラー
//Uri webUri = new Uri("https://appdev.microsoft.com/ja-JP/"); // 404エラー
//Uri webUri = new Uri("http://notexist.example.com/"); // リモート名を解決できないエラー
// GetWebPageAsyncメソッドを呼び出す
Task<string> webTask = GetWebPageAsync(webUri);
webTask.Wait(); // Mainメソッドではawaitできないので、処理が完了するまで待機する
string result = webTask.Result; // 結果を取得
timer.Stop();
Console.WriteLine("{0:0.000}秒", timer.Elapsed.TotalSeconds);
Console.WriteLine();
// 取得結果を使った処理
if (result != null)
{
// サンプルとして、取得したHTMLデータの<h1>タグ以降を一定長だけ表示
Console.WriteLine("========");
int h1pos = result.IndexOf("<h1", StringComparison.OrdinalIgnoreCase);
if (h1pos < 0)
h1pos = 0;
const int MaxLength = 720;
int len = result.Length - h1pos;
if (len > MaxLength)
len = MaxLength;
Console.WriteLine(result.Substring(h1pos, len));
Console.WriteLine("========");
}
#if DEBUG
Console.ReadKey();
#endif
}
static async Task<string> GetWebPageAsync(Uri uri)
{
……省略……
}
}
Imports System.Net.Http
Module Module1
Sub Main()
Console.WriteLine("HttpClientクラスでWebページを取得する")
' 時間計測用のタイマー
Dim timer = New System.Diagnostics.Stopwatch()
timer.Start()
' 取得したいWebページのURI
Dim webUri As Uri = New Uri("http://dev.windows.com/ja-jp")
'Dim webUri As Uri = New Uri("https://dev.windows.com/ja-jp") ' HTTPSでもOK!
'Dim webUri As Uri = New Uri("https://dev.windows.com/") ' デフォルトではリダイレクト先を取得してくれる
'Dim webUri As Uri = New Uri("https://appdev.microsoft.com/") ' 403エラー
'Dim webUri As Uri = New Uri("https://appdev.microsoft.com/ja-JP/") ' 404エラー
'Dim webUri As Uri = New Uri("http://notexist.example.com/") ' リモート名を解決できませんでした。
' GetWebPageAsyncメソッドを呼び出す
Dim webTask As Task(Of String) = GetWebPageAsync(webUri)
webTask.Wait() ' Mainメソッドではawaitできないので、処理が完了するまで待機する
Dim result As String = webTask.Result ' 結果を取得
timer.Stop()
Console.WriteLine("{0:0.000}秒", timer.Elapsed.TotalSeconds)
Console.WriteLine()
' 取得結果を使った処理
If (result IsNot Nothing) Then
' サンプルとして、取得したHTMLデータの<h1>タグ以降を一定長だけ表示
Console.WriteLine("========")
Dim h1pos As Integer = result.IndexOf("<h1", StringComparison.OrdinalIgnoreCase)
If (h1pos < 0) Then
h1pos = 0
End If
Const MaxLength As Integer = 720
Dim len As Integer = result.Length - h1pos
If (len > MaxLength) Then
len = MaxLength
End If
Console.WriteLine(result.Substring(h1pos, len))
Console.WriteLine("========")
End If
#If DEBUG Then
Console.ReadKey()
#End If
End Sub
Async Function GetWebPageAsync(uri As Uri) As Task(Of String)
……省略……
End Function
End Module
上のコードを試してみよう。そのまま実行すると、次の画像のようにWebページの内容が表示される。
コード中でコメントにしてある他のURLも試してみよう。まず、HTTPS対応とリダイレクト対応であるが、どちらも上の画像と同じ出力が得られ、対応していると確認できる。HTTPSは、表示されることで対応していると分かるだろう。リダイレクトは、日本語のページが表示されることで分かる(リダイレクトされなければ英語ページになる。受け入れ言語をセットしている部分を削って試してほしい)。
次に、エラーになるURLも見ておこう(次の画像)。画像の上から順に、403エラー/404エラー(どちらもWebサーバーでのエラー)、そしてDNSでの名前解決に失敗したときのエラーである。
また、「GetWebPageAsync」メソッドの中でセットしているタイムアウト時間を極端に短くすると(例えば0.1秒)、次の画像のようなエラーが得られる。
*1 Visual Studio 2012で導入されたasync/awaitキーワードを用いる非同期プログラミングについて、詳しくは次のMSDNのドキュメントや@ITの記事を参照していただきたい。
*2 REST(REpresentational State Transfer)については、次の記事が参考になるだろう。
*3 クッキーをセットするには、HttpMessageHandlerクラスを継承しているHttpClientHandlerクラス(System.Net.Http名前空間)を利用する。HttpClientHandlerオブジェクトを作り、そのCookieContainerプロパティにクッキーをセットする。そして、そのHttpClientHandlerオブジェクトをHttpClientクラスのコンストラクター引数に渡せばよい。クッキーをセットするこの方法は、以前より少々面倒になった部分である。なお、プロキシの設定やリダイレクト可否の設定も同様にして行う。
*4 プロキシを使う方法が次の記事に掲載されている。
*5 ユーザー認証の必要なサイトにアクセスする例として、OAuth認証を使ってtwitterにアクセスする方法が次の記事に掲載されている(英文)。
利用可能バージョン:.NET Framework 4.5以降
カテゴリ:クラスライブラリ 処理対象:ネットワーク
使用ライブラリ:HttpClientクラス(System.Net.Http名前空間)
関連TIPS:WebClientクラスでWebページを取得するには?関連TIPS:WebRequest/WebResponseクラスでWebページを取得するには?
Copyright© Digital Advantage Corp. All Rights Reserved.