Setup IIS10 to serve pre-compressed files
Asked Answered
F

2

8

my angular2 projects build pre-compressed gzip files for my web-app but my IIS only serves the normal ".js" files instead of the compressed ".gzip" files. My browser is willing to accept gzip.

What is the correct setting for IIS to allow gzip responses?

I already searched google/SO/SU but only found solutions for not "pre-compressed" content.

Furring answered 20/2, 2018 at 16:20 Comment(0)
I
9

A more neat and elegant solution:

NOTICE: The file extension .gzip seems strange, in general, we name a gziped file as .gz, so in this example, we use .gz instead .gzip, if you insist on .gzip, just replace all the extensions in the following config file.


Code first, this is what all we need for web.config

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <staticContent>
      <remove fileExtension=".js.gz" />
      <remove fileExtension=".css.gz" />
      <remove fileExtension=".png.gz" />
      <remove fileExtension=".jpg.gz" />
      <remove fileExtension=".gif.gz" />
      <remove fileExtension=".svg.gz" />
      <remove fileExtension=".html.gz" />
      <remove fileExtension=".json.gz" />
      <mimeMap fileExtension=".js.gz" mimeType="application/javascript" />
      <mimeMap fileExtension=".css.gz" mimeType="text/css" />
      <mimeMap fileExtension=".png.gz" mimeType="image/png" />
      <mimeMap fileExtension=".jpg.gz" mimeType="image/jpeg" />
      <mimeMap fileExtension=".gif.gz" mimeType="image/gif" />
      <mimeMap fileExtension=".svg.gz" mimeType="image/svg+xml" />
      <mimeMap fileExtension=".html.gz" mimeType="text/html" />
      <mimeMap fileExtension=".json.gz" mimeType="application/json" />
    </staticContent>
  
    <rewrite>
      <outboundRules rewriteBeforeCache="true">
        <rule name="Custom gzip file header">
          <match serverVariable="RESPONSE_CONTENT_ENCODING" pattern=".*" />
          <conditions>
            <add input="{REQUEST_URI}" pattern="\.gz$" />
          </conditions>
          <action type="Rewrite" value="gzip"/>
        </rule>
      </outboundRules>
      
      <rules>
        <rule name="Rewrite gzip file">
          <match url="(.*)"/>
          <conditions>
            <add input="{HTTP_ACCEPT_ENCODING}" pattern="gzip" />
            <add input="{REQUEST_FILENAME}.gz" matchType="IsFile" />
          </conditions>
          <action type="Rewrite" url="{R:1}.gz" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

And, here are how it works:

In order to achieve a successful gziped data transmission, we need:

  • Client side accept gziped data, Accept-Encoding
  • Response with a header with Content-Encoding
  • Proper MIME type, as the original file is, BUT NOT application/gzip
  • Gziped file

The four conditions must be satisfied at the same time.

If you send an uncompressed file with Content-Encoding: gzip, the browser will return an error;

If you send a compressed file without a Content-Encoding header, or a mismatched MIME type, the page may return some Zenith Star's text.

So what we are doing is:

  • Redefine each type of gziped file's MIME
  • If the client side accept a gziped file, then redirect the response file to a gziped version on the server side directly (not 302/303/307 response)
  • Rewrite the header of the response header of Content-Encoding, only if the client side send the header Accept-Encoding

This solution works on my IIS7, not sure if it will also work on IIS10.

If you met any problem, let me know :D

Ionize answered 17/3, 2018 at 10:27 Comment(7)
Thanks for pointing this out. Unfortunately, I couldn't get this to work in IIS 10 (which you said it may not). Additionally, I ended up discovering that the answer we were looking at here #45199713 doesn't work in all browsers. FireFox (as an example) won't load any .js files with the error SyntaxError: illegal character. I wasn't able to confirm or deny if your solution has this same issue though.Iconolatry
@Iconolatry hmmmmmm, I just tried this config on my windows 10, with IIS 10, it runs well...Ionize
And it served the pre-compressed files? Not dynamically compressed ones? That's where I couldn't get it to go. It served compressed files but they we're ones it dynamically compressed... showing a file size somewhat larger than my pre-compressed ones. If so, I'll give this another go and let you know.Iconolatry
@Iconolatry try disable dynamic gzip first, if it works after this, we may figure out another solution.Ionize
yes I did both (at different times troubleshooting) when I remove dynamic, it just serves the full file which is much larger than either the pre-compressed or the dynamically compressed. What's odd is that the other answer setup (which is similar but less robust than yours) works (in everything but Firefox) in IIS10. So, I'm not sure why this wouldn't work too.Iconolatry
This was very helpful. I had to install the dynamic compression module to take advantage of the benefits. Thanks for sharing your knowledge and ideas!Calliper
Also you need to have URL Rewrite installed on your IIS. Otherwise HTTP error 500.19 with HRESULT 0x8007000d will raise.Prospect
F
2

After a long time searching I found a workaround with URL-Rewrite.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <httpProtocol>
            <customHeaders>
                <remove name="X-Powered-By" />
            </customHeaders>
        </httpProtocol>
        <rewrite>
            <rules>
                <clear />
                <rule name="Https redirect" stopProcessing="true">
                    <match url="(.*)" />
                    <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
                        <add input="{HTTP_HOST}" pattern="^domain.com$" />
                        <add input="{HTTPS}" pattern="^OFF$" />
                    </conditions>
                    <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" />
                </rule>
                <rule name="LetsEncrypt">
                    <match url=".well-known/acme-challenge/*" />
                    <conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
                    <action type="None" />
                </rule>
                <rule name="Angular Routes" stopProcessing="true">
                    <match url="(.*)" />
                    <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
                        <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
                        <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
                        <add input="{REQUEST_URI}" pattern="^/(api)" negate="true" />
                    </conditions>
                    <action type="Rewrite" url="/" />
                </rule>
                <rule name="br_rewrite" enabled="true" stopProcessing="true">
                    <match url="(.*).(js$|svg|css)" />
                    <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
                        <add input="{HTTP_ACCEPT_ENCODING}" pattern="br" />
                        <add input="{REQUEST_FILENAME}" matchType="IsFile" />
                    </conditions>
                    <action type="Rewrite" url="{R:1}.{R:2}.br" logRewrittenUrl="true" />
                </rule>
                <rule name="gzip_rewrite" enabled="true" stopProcessing="true">
                    <match url="(.*).(js$|svg|css)" />
                    <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
                        <add input="{HTTP_ACCEPT_ENCODING}" pattern="gzip" />
                        <add input="{REQUEST_FILENAME}" matchType="IsFile" />
                    </conditions>
                    <action type="Rewrite" url="{R:1}.{R:2}.gz" logRewrittenUrl="true" />
                </rule>
            </rules>
            <outboundRules rewriteBeforeCache="true">
                <rule name="Remove Server header" enabled="true">
                    <match serverVariable="RESPONSE_Server" pattern=".+" />
                    <action type="Rewrite" value="" />
                </rule>
                <rule name="Rewrite content-encoding header gzip" preCondition="IsGZ" enabled="true" stopProcessing="false">
                    <match serverVariable="RESPONSE_CONTENT_ENCODING" pattern=".*" />
                    <action type="Rewrite" value="gzip" />
                </rule>
                <rule name="Rewrite content-encoding header br" preCondition="IsBR" enabled="true" stopProcessing="false">
                    <match serverVariable="RESPONSE_CONTENT_ENCODING" pattern=".*" />
                    <action type="Rewrite" value="br" />
                </rule>
                <rule name="css content type" preCondition="IsCSS" enabled="true" stopProcessing="false">
                    <match serverVariable="RESPONSE_CONTENT_TYPE" pattern="(.*)" />
                    <action type="Rewrite" value="text/css" />
                </rule>
                <rule name="js content type" preCondition="IsJS" enabled="true" stopProcessing="false">
                    <match serverVariable="RESPONSE_CONTENT_TYPE" pattern="(.*)" />
                    <action type="Rewrite" value="application/javascript" />
                </rule>
                <rule name="svg content type" preCondition="IsSVG" enabled="true" stopProcessing="false">
                    <match serverVariable="RESPONSE_CONTENT_TYPE" pattern="(.*)" />
                    <action type="Rewrite" value="image/svg+xml" />
                </rule>
                <preConditions>
                    <preCondition name="IsGZ">
                        <add input="{URL}" pattern="\.gz$" />
                    </preCondition>
                    <preCondition name="IsBR">
                        <add input="{URL}" pattern="\.br$" />
                    </preCondition>
                    <preCondition name="IsCSS">
                        <add input="{URL}" pattern="css" />
                    </preCondition>
                    <preCondition name="IsJS">
                        <add input="{URL}" pattern="js" />
                    </preCondition>
                    <preCondition name="IsSVG">
                        <add input="{URL}" pattern="svg" />
                    </preCondition>
                </preConditions>
            </outboundRules>
        </rewrite>
        <urlCompression doStaticCompression="true" doDynamicCompression="false" />
        <httpCompression sendCacheHeaders="false" />
        <staticContent>
            <mimeMap fileExtension=".br" mimeType="application/brotli" />
            <clientCache cacheControlMode="UseMaxAge" />
        </staticContent>
    </system.webServer>
</configuration>

It's successfull handels BR and GZIP requests for pre-build angular files (JS, CSS, SVG).

I hope this helps someone else. If you know a better solution let me know.

Furring answered 26/2, 2018 at 12:15 Comment(4)
Using this workaround, I could see Chrome uses brotli files now. But when I remove the rules for brotli and keep gzip, the compression doesn't work in IIS 10. @FurringPinfold
I'm not an expert for IIS so my solution is try by error (most likely very error-prone). Have you tried the solution from @Losses Don ? He offered a cleaner solution that might come without the problem if you only need gzip.Furring
I did. Using that it seemed to serve gz files (I understood by the size), but occurs syntax errors due the absence of the header content-encoding: gzip where browser is unable to decode.Pinfold
I figured that SSL needs to be enabled to get the pre-compressed files, even when the server is configured as mentioned.Pinfold

© 2022 - 2024 — McMap. All rights reserved.