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.

Alex
  • 1,857
  • 3
  • 36
  • 51

2 Answers2

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

simon04
  • 3,054
  • 29
  • 25
Losses Don
  • 923
  • 10
  • 14
  • 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 https://stackoverflow.com/questions/45199213/angulars-pre-gzipped-files-are-not-served-via-iis/45397352?noredirect=1#comment85923508_45397352 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. – mikeo Mar 30 '18 at 20:39
  • @mikeo hmmmmmm, I just tried this config on my windows 10, with IIS 10, it runs well... – Losses Don Mar 31 '18 at 10:52
  • 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. – mikeo Mar 31 '18 at 18:45
  • @mikeo try disable dynamic gzip first, if it works after this, we may figure out another solution. – Losses Don Apr 01 '18 at 03:51
  • 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. – mikeo Apr 02 '18 at 16:38
  • 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! – omostan Apr 04 '20 at 13:36
  • Also you need to have `URL Rewrite` installed on your IIS. Otherwise HTTP error 500.19 with HRESULT 0x8007000d will raise. – Rzassar Mar 15 '23 at 12:27
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.

Alex
  • 1,857
  • 3
  • 36
  • 51
  • 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. @Alex – Uthpala Pathirana Aug 07 '19 at 07:52
  • 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. – Alex Aug 07 '19 at 12:59
  • 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. – Uthpala Pathirana Aug 08 '19 at 02:38
  • I figured that SSL needs to be enabled to get the pre-compressed files, even when the server is configured as mentioned. – Uthpala Pathirana Aug 15 '19 at 07:07