tech.guitarrapc.cóm

Technical updates

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

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

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

image

Cache Control Header について

参考になるサイトです

https://yasuda.iobb.net/wordpress/archives/9480

https://garafu.blogspot.jp/2013/05/http.html

ここを参考に、キャッシュさせたいもの、させたくないものにどんな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

https://msdn.microsoft.com/ja-jp/library/e1f13641%28v=vs.85%29.aspx

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

image

今回の場合は、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です。

https://msdn.microsoft.com/en-us/library/ms690556.aspx

https://www.iis.net/configreference/system.webserver/httpprotocol/customheaders

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

例えば、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で調整せずにやっても意味を成しません。

https://www.iis.net/configreference/system.webserver/caching

.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 の調整

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

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

https://stackoverflow.com/questions/2195266/how-to-configure-static-content-cache-per-folder-and-extension-in-iis7

パスごとに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書き換え / プロキシ)を組んだりが簡単にできるようになります。

https://www.iis.net/downloads/microsoft/application-request-routing

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 *3


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

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

*2:あるいはその逆

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