読者です 読者をやめる 読者になる 読者になる

tech.guitarrapc.cóm

C#, PowerShell, Unity, Cloud Platfrom Technical Update and Features

IIS 8.5 における 静的コンテンツのキャッシュコントロールヘッダー変更とARR

Windows ASP.NET IIS

Windows Server 2012 では IIS 8。これが Windows Server 2012 R2 では IIS 8.5 になります。

なにが変わるかって? Static Content (静的コンテンツ) のキャッシュコントロールヘッダーがなんか変わるんですね-しょぼん。

今回はぐぐっても錯綜した情報が垣間見える、ASP.NET MVC における IIS の Static Content に関するキャッシュコントロールヘッダーの変更についてです。

何気に IIS でほげもげするのってあんまりまとまってないんですね。特にARR絡むと。

目次

Cache Control Header の確認

Cache Control Header を確認するなら、Chrome や IE の開発者ツールを使うと楽でしょう。たぶんきっと。

F12 > Networkタブ > 適当なコンテンツ > Headers > Response Header

こんな感じで 応答ヘッダーが見えます。

f:id:guitarrapc_tech:20141125080714p:plain

Cache Control Header について

参考になるサイトです

ブラウザ・キャッシュ「Etag:」「Cache-Control:」を停止してみた | 自宅サーバ練習用ブログ

ここを参考に、キャッシュさせたいもの、させたくないものにどんな Cache Control Header を設定すればいいのか頭を整理するといいでしょう。

IIS における Cache Control Header

web.config で設定しよう

IIS のCache Control Header ですが、通常は IIS全体(ApplicationHost.config) ではなく、サイト単位(web.config)で行うでしょう。

web.config でやらない理由がないですね。

IIS 8.5 における既定の Cache Control Header

まず Cache Control Header をそもそも付けるかどうかですが、これは System.Web ディレクティブ配下の httpRuntime 要素である sendCacheControlHeader に boolean で指定します。

デフォルトは true で、そのままだと Cache Control Header に private が送信されます。つまり Cache されるかはブラウザの挙動に依存します。*1

これは reference Sourceに記述の通りですね。

f:id:guitarrapc_tech:20141229220817p:plain

今回の場合は、Cache Control Header は送信されないといけないのでそのままでいいでしょう。Private の削除など制御は別途行います。

なので、逆に以下のように system.web ディレクティブに sendCacheControlHeader="false" がかかれていたらダメですね。

 <system.web>
    <httpRuntime targetFramework="4.5" sendCacheControlHeader="false" />
 </system.web>

Cloud Front での Cache など Origin のヘッダを尊重する Object Caching (CDN) の場合、むしろ false である方が望ましかったりするのですが今回はスルーで。

Custom Headers を設定する

View単位の設定

Cache Control Header をカスタムしたい。そんな時に、動的コンテンツ(ASP.NET でコードレベルでページごとに制御する) なら各View側のコードで指定すればできます

全View統一の設定

全Viewで統一の動きなら web.config です。

そこで使うのが、system.webServer ディレクティブ、 httpProtocolにある customHeaders です。

customHeaders Element for httpProtocol [IIS Settings Schema]

Custom Headers <customHeaders> : The Official Microsoft IIS Site

ここでは、任意のヘッダーを操作可能です。

例えば、X-Custom-Name で MyCustomValue 追加で返すならこうです。

<configuration>
   <system.webServer>
      <httpProtocol>
         <customHeaders>
            <add name="X-Custom-Name" value="MyCustomValue" />
         </customHeaders>
      </httpProtocol>
   </system.webServer>
</configuration>

customHeaders は、add, remove, clear の各attribute を持っているので、これを使って自由に操作できますす。

たとえば、Cache させたくないなら以下のように no-cache, no-store, must-revalidate を足せるでしょう。

Pragma をなくすなどいろんな説があるのですが.... いったん付けておきます。

     <httpProtocol>
       <customHeaders>
         <add name="Cache-Control" value="no-cache, no-store, must-revalidate" />
         <add name="Pragma" value="no-cache" />
         <add name="Expires" value="-1" />
       </customHeaders>
     </httpProtocol>

逆に Cache を常に有効にするなら、Cache-Control に、Public を付ければいいですね。Expiresなどで期限制御でしょう。

ただし、ここでやってしまうと全StaticContent に影響します。拡張子やパスごとに調整したくなったら 更に調整が必要でしょう。

拡張子に応じた Cacheの調整

Custom Headers で Cache 制御を書いたら、IIS reference の caching で調整ができます。逆にいうと Custom Headers で調整せずにやっても意味を成しません。

Caching <caching> : The Official Microsoft IIS Site

.asp の拡張子を対象にキャッシュ調整とかですね。

<configuration>
   <system.webServer>
      <caching enabled="true" enableKernelCache="true">
         <profiles>
            <add extension=".asp" policy="CacheUntilChange" kernelCachePolicy="CacheUntilChange" />
         </profiles>
      </caching>
   </system.webServer>
</configuration>

同様に KernelCache が云々もできたりなんとか。

<configuration>
   <system.webServer>
      <caching enabled="true" enableKernelCache="true" maxCacheSize="1000" maxResponseSize="512000"/>
   </system.webServer>
</configuration>

まぁでも、ひと昔ならともかく今は拡張子制御は微妙ですね…。

パスに応じたCache の調整

拡張子がによるキャッシュ制御が微妙とみなす理由の一つが、現在はパスで静的コンテンツを区切るパターンが多いからです。実際 minifyしてると拡張子がないファイルで展開することもありますしね。

そこで、全体ではCache Control を無効に *2しておいて、特定のパスだけ Cache を効かせる*3。といった、全体と一部パスで挙動を変化させる時に使えるのが location path=hogemoge です。

iis - How to configure static content cache per folder and extension in IIS7? - Stack Overflow

パスごとにsystem.webServer の調整ができたりするので、これを使えば パスごとの Cache Control Header を書き替えなおすこともできます。

例えば、全体では Cache Control を no-store, no-cache にしていても、 /static 配下のコンテンツはキャッシュしたいなら、全体に対するsystem.webServer ディレクティブの下に

    <system.webServer>
      <httpProtocol>
        <customHeaders>
          <add name="Cache-Control" value="no-cache, no-store, must-revalidate" />
          <add name="Pragma" value="no-cache" />
          <add name="Expires" value="-1" />
        </customHeaders>
      </httpProtocol>
    </system.webServer>

path を指定した記述を web.config で追加すればいけます。/static配下ならこうです。

    <location path="static">
      <system.webServer>
        <staticContent>
          <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="03:00:00" />
        </staticContent>
        <httpProtocol>
          <customHeaders>
            <remove name="Cache-Control" />
            <remove name="Pragma" />
            <remove name="Expires" />
            <add name="Cache-Control" value="public" />
          </customHeaders>
      </httpProtocol>
      </system.webServer>
    </location>

これで IIS/ASP.NET を使っている時の Cache Control Header は制御できますね。

ARRがあるときの web.config に注意

ARR - Application Request Routing を使うと、IIS 自身で A WebSite から B WebSite にリダイレクト(URL 書き換え / プロキシ)を組んだりが簡単にできるようになります。

ARRを使うことで、ASP.NET の本体の前にいくつかのプロキシサイトを構成しておいて、「特定の状態の場合はアプリケーションまでルーティングさせない」などの構成が可能になります。結構神で素晴らしいわけです。

ProxySite - Application

ただし、ARR でルーティングするために作ったサイトはASP.NETなど本来やりたいWebサイトとは独立した Webサイトですよね。そのため、ASP.NET 本体などの前にProxy用のWebサイトがある時、ASP.NETの応答が外側のプロキシサイトのweb.config で上書かれるんですねー。はい注意。

なので、ASP.NET側で web.config を使って、あるいはコードでキャッシュ制御を書いても、プロキシサイトの web.config を調整しておかないといけません。

知らなくてはまりましたのです。

まとめ

web.config かわいいよ。web.config *4

*1:Cache-Control: private のみが指定されている場合、何らかのキャッシュへの記録が行われるおそれがある。実際Chromeではキャッシュされないのに他ではキャッシュされたりね

*2:customHeaders で Cache-Control に no-cache とか指定

*3:あるいはその逆

*4:GUIなんてさわりません