ASP.NETで特定のリクエストに動的にHTTPヘッダを設定する方法


概要

 全てのリクエストに対してHeaderを追加する場合は、Web.configに<customHeaders>を追加すれば済みます(下記)。しかし、リクエストに応じてヘッダを設定したい場合は使えないです。

Web.config
<configuration>
 <system.webServer>
    <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Allow-Origin" value="*"/>
      </customHeaders>
    </httpProtocol>
  </system.webServer>
</configuration>

 Viewを返すControllerでHeaderを追加すれば済む話ではありますが、いちいち追加するのはあまり綺麗でない上に静的に配信するファイルに対しては有効でないです。
 今回は、Moduleを用いてリクエスト毎に実行されるイベントを作成することで用いて実現します。

Moduleとは

 Moduleを使用すると、各要求の途中段階で処理を割り込ませることが可能です。これにより、要求に対して特定の動作をすることが可能です。

ソースコード

using System;
using System.Web;
using System.Linq;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace App.Modules
{
    public class HTTPHeaderModule : IHttpModule
    {
        public void Init(HttpApplication context)
        {
            context.PreSendRequestHeaders += AddHeaderFromURL;
        }

        /// <summary>
        /// HeaderをURLに応じて追加する
        /// </summary>
        private void AddHeaderFromURL(object sender, EventArgs e)
        {
            HttpRequest request = (HttpRequest)sender.GetType().GetProperty("Request").GetValue(sender);
            HttpResponse responce = (HttpResponse)sender.GetType().GetProperty("Response").GetValue(sender);
            //リクエストのURL
            var url = request.Url;
            //リクエストの絶対パスの先頭が/apiだったら            
            if (Regex.IsMatch(url.AbsolutePath, "^/api", RegexOptions.IgnoreCase))
            {
                if (request.Headers.AllKeys.Contains("Origin"))
                {
                    //リクエスト元(オリジン)のURL
                    Uri hostUrl = new Uri(request.Headers["Origin"]);
                    if (IsAllowedHost(hostUrl.Host))
                    {
                        //リクエストのオリジンを許可
                        responce.Headers.Add("Access-Control-Allow-Origin", hostUrl.GetLeftPart(UriPartial.Authority));
                    }
                }
            }
        }

        public void Dispose(){}

        private bool IsAllowedHost(string host)
        {
            return Regex.IsMatch(host, ".*twitter\\.com", RegexOptions.IgnoreCase);
        }
    }
}
Web.config
<configuration>
  <system.webServer>
    <modules>
      <add name="HTTPHeaderModule" type="App.Modules.HTTPHeaderModule" />
    </modules>
  </system.webServer>
</configuration>

やっていること

 Initでヘッダ送信前に実行されるよう、AddHeaderFromURL関数を登録しています。
AddHeaderFromURL関数では、senderにリクエストやレスポンスが入ったオブジェクトが渡されるので、それをReflectionで取り出してあげています。(HttpContext.Currentにもリクエストやレスポンスが入っていて、それを用いても同様の動作をします。しかし、ApiControllerを用いてレスポンスを返す際にはnullになることがある 1 ようなので、senderから取得しています。)
分かりにくいですが、Reflectionで取り出したものも参照をコピーしてきているだけなので、これの中を変更してもsenderは変更されます。よって、特定の条件にマッチした場合、取り出したレスポンス内のheaderに追加しています。


  1.