かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

HttpClientクラスがリダイレクトをよきに計らってくれるのをどうにかしたい

.NET Framework 4.5で追加された最高のHttpを扱うためのクラス!その名はSystem.Net.Http.HttpClientです。System.Net.Httpを参照に加えるだけで追加できるお手軽なうえ、一般的な処理は最初からやってくれるといういいやつです。
その中の1つにサーバーからリダイレクトが返ってきたら、勝手にリダイレクト先にリクエスト投げて結果をとってきて返してくれるというのがあります。

リダイレクトする処理として、ASP.NET MVC 4でこんなコントローラを作りました。

using System.Web.Mvc;

public class HomeController : Controller
{
    public ActionResult Index()
    {
        // GetDataにリダイレクトするだけ
        return RedirectToAction("GetData");
    }

    public ActionResult GetData()
    {
        // 適当にJsonを返す
        return Json(new { message = "Hello worl" }, JsonRequestBehavior.AllowGet);
    }
}

このコントローラを作ったプロジェクトにhttp://localhost:13168/でアクセスすると一度リダイレクトされてJSON形式のデータがダウンロードされます。(URLのポート番号は実際に作成したプロジェクトのものに読み替えてください)
ここで、コンソールアプリケーションを作ってSystem.Net.Httpを参照に追加して以下のようなコードを書いて実行してみます。

using System;
using System.Net;
using System.Net.Http;

class Program
{
    static void Main(string[] args)
    {
        var client = new HttpClient();

        // アクセス先URL
        var uri = new Uri("http://localhost:13168");
        var r = client.GetAsync(uri).Result;
        Console.WriteLine(r);
        switch (r.StatusCode)
        {
            case HttpStatusCode.OK:
                // 成功したので表示
                Console.WriteLine(r.Content.ReadAsStringAsync().Result);
                break;
            case HttpStatusCode.Found:
                Console.WriteLine("リダイレクトです");
                // 自前リダイレクト
                Console.WriteLine(
                    // リダイレクト先にアクセスしてデータをとってくる
                    client.GetAsync(new Uri(uri, r.Headers.Location)).Result.Content.ReadAsStringAsync().Result);
                break;
            default:
                // 今回は対応しない
                throw new InvalidOperationException();
        }
    }
}

実行すると、リダイレクトを良きに計らってJSONをとってきてくれてることが確認できます。(プログラムでいうswitch文のOKの処理を通っている)

StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
  X-AspNetMvc-Version: 4.0
  X-SourceFiles: = ...省略...
  Cache-Control: private
  Date: Tue, 25 Dec 2012 14:39:16 GMT
  Server: Microsoft-IIS/8.0
  X-AspNet-Version: 4.0.30319
  X-Powered-By: ASP.NET
  Content-Length: 24
  Content-Type: application/json; charset=utf-8
}
{"message":"Hello worl"}

これでは困る人もいる

とまぁ、これは大多数の人が歓迎すべき挙動だと思います。でもあえてリダイレクトがあったときの処理をハンドリングしたいという人もいるでしょう。そういう人は、HttpWebRequestを使って低レベルなAPIをバシバシ叩いて…という冷たい対応ではないのがHttpClientのいいところです。HttpClientはコンストラクタにHttpWebMessageHandlerを指定するものがあります。こいつに設定をカスタマイズしたHttpClientHandlerを渡してやることでいろいろな挙動を制御できます。例えば、今回のリダイレクトでいうとAllowAutoRedirectという、そのものズバリなプロパティがあってこいつをfalseにすることで、自動でリダイレクトの処理をしてくれなくなります。試してみましょう。先ほどのコードにちょこっと手を入れます。

using System;
using System.Net;
using System.Net.Http;

class Program
{
    static void Main(string[] args)
    {
        // HttpClientに設定するハンドラ
        var handler = new HttpClientHandler
        {
            // 自動でリダイレクトする機能をOFFにする
            AllowAutoRedirect = false
        };

        // ハンドラーを指定してHttpClientを作る
        var client = new HttpClient(handler);

        // 続きは同じ
        // アクセス先URL
        var uri = new Uri("http://localhost:13168");
        var r = client.GetAsync(uri).Result;
        Console.WriteLine(r);
        switch (r.StatusCode)
        {
            case HttpStatusCode.OK:
                // 成功したので表示
                Console.WriteLine(r.Content.ReadAsStringAsync().Result);
                break;
            case HttpStatusCode.Found:
                Console.WriteLine("リダイレクトです");
                // 自前リダイレクト
                Console.WriteLine(
                    // リダイレクト先にアクセスしてデータをとってくる
                    client.GetAsync(new Uri(uri, r.Headers.Location)).Result.Content.ReadAsStringAsync().Result);
                break;
            default:
                // 今回は対応しない
                throw new InvalidOperationException();
        }
    }
}

先頭でHttpClientHandlerを作成して、AllowAutoRedirectをfalseにしてHttpClientのコンストラクタに渡している以外は同じコードです。実行結果を見てみるとリダイレクトが自動で行われていないことが確認できます。

StatusCode: 302, ReasonPhrase: 'Found', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
  X-AspNetMvc-Version: 4.0
  X-SourceFiles: ...省略...
  Cache-Control: private
  Date: Tue, 25 Dec 2012 14:51:52 GMT
  Location: /Home/GetData
  Server: Microsoft-IIS/8.0
  X-AspNet-Version: 4.0.30319
  X-Powered-By: ASP.NET
  Content-Length: 130
  Content-Type: text/html; charset=utf-8
}
リダイレクトです
{"message":"Hello worl"}

その他に

HttpClientHandlerクラスには、リダイレクト以外にも色々な動作をカスタマイズするプロパティが公開されています。認証情報を載せるCredentialsプロパティや、クッキーをサポートするかどうか設定するためのUseCookiesプロパティなどがあります。HttpClientのデフォルトの挙動をカスタマイズしたいという人は、まずこのHttpClientHandlerに該当するプロパティが無いか探すところから始めてみるといいと思います。
どうしても自分の望む動きにできないというときは、HttpWebMessageHandlerを継承してSendAsyncメソッドをオーバーライドして自分でカスタムする方法もあります。