URL Rewrite – Protocol (http/https) in the Action

IIS URL Rewrite supports server variables for pretty much every part of the URL and http header. However, there is one commonly used server variable that isn’t readily available.  That’s the protocol—HTTP or HTTPS.

You can easily check if a page request uses HTTP or HTTPS, but that only works in the conditions part of the rule.  There isn’t a variable available to dynamically set the protocol in the action part of the rule.  What I wish is that there would be a variable like {HTTP_PROTOCOL} which would have a value of ‘HTTP’ or ‘HTTPS’.  There is a server variable called {HTTPS}, but the values of ‘on’ and ‘off’ aren’t practical in the action.  You can also use {SERVER_PORT} or {SERVER_PORT_SECURE}, but again, they aren’t useful in the action.

Let me illustrate.  The following rule will redirect traffic for http(s)://localtest.me/ to http://www.localtest.me/.

<rule name="Redirect to www">
  <match url="(.*)" />
  <conditions>
    <add input="{HTTP_HOST}" pattern="^localtest\.me$" />
  </conditions>
  <action type="Redirect" url="http://www.localtest.me/{R:1}" />
</rule>

The problem is that it forces the request to HTTP even if the original request was for HTTPS.

Interestingly enough, I planned to blog about this topic this week when I noticed in my twitter feed yesterday that Jeff Graves, a former colleague of mine, just wrote an excellent blog post about this very topic.  He beat me to the punch by just a couple days.  However, I figured I would still write my blog post on this topic.  While his solution is a excellent one, I personally handle this another way most of the time.  Plus, it’s a commonly asked question that isn’t documented well enough on the web yet, so having another article on the web won’t hurt.

I can think of four different ways to handle this, and depending on your situation you may lean towards any of the four.  Don’t let the choices overwhelm you though.  Let’s keep it simple, Option 1 is what I use most of the time, Option 2 is what Jeff proposed and is the safest option, and Option 3 and Option 4 need only be considered if you have a more unique situation.  All four options will work for most situations.

Option 1 – CACHE_URL, single rule

There is a server variable that has the protocol in it; {CACHE_URL}.  This server variable contains the entire URL string (e.g. http://www.localtest.me:80/info.aspx?id=5)  All we need to do is extract the HTTP or HTTPS and we’ll be set. This tends to be my preferred way to handle this situation.

Indeed, Jeff did briefly mention this in his blog post:

… you could use a condition on the CACHE_URL variable and a back reference in the rewritten URL. The problem there is that you then need to match all of the conditions which could be a problem if your rule depends on a logical “or” match for conditions.

Thus the problem.  If you have multiple conditions set to “Match Any” rather than “Match All” then this option won’t work.  However, I find that 95% of all rules that I write use “Match All” and therefore, being the lazy administrator that I am I like this simple solution that only requires adding a single condition to a rule.  The caveat is that if you use “Match Any” then you must consider one of the next two options.

Enough with the preamble.  Here’s how it works.  Add a condition that checks for {CACHE_URL} with a pattern of “^(.+)://” like so:

image

How you have a back-reference to the part before the ://, which is our treasured HTTP or HTTPS.  In URL Rewrite 2.0 or greater you can check the “Track capture groups across conditions”, make that condition the first condition, and you have yourself a back-reference of {C:1}.

The “Redirect to www” example with support for maintaining the protocol, will become:

<rule name="Redirect to www" stopProcessing="true">
  <match url="(.*)" />
  <conditions trackAllCaptures="true">
    <add input="{CACHE_URL}" pattern="^(.+)://" />
    <add input="{HTTP_HOST}" pattern="^localtest\.me$" />
  </conditions>
  <action type="Redirect" url="{C:1}://www.localtest.me/{R:1}" />
</rule>

It’s not as easy as it would be if Microsoft gave us a built-in {HTTP_PROTOCOL} variable, but it’s pretty close.

I also like this option since I often create rule examples for other people and this type of rule is portable since it’s self-contained within a single rule.

Option 2 – Using a Rewrite Map

For a safer rule that works for both “Match Any” and “Match All” situations, you can use the Rewrite Map solution that Jeff proposed.  It’s a perfectly good solution with the only drawback being the ever so slight extra effort to set it up since you need to create a rewrite map before you create the rule.  In other words, if you choose to use this as your sole method of handling the protocol, you’ll be safe.

After you create a Rewrite Map called MapProtocol, you can use “{MapProtocol:{HTTPS}}” for the protocol within any rule action.  Following is an example using a Rewrite Map.

<rewrite>
  <rules>
    <rule name="Redirect to www" stopProcessing="true">
      <match url="(.*)" />
      <conditions trackAllCaptures="false">
        <add input="{HTTP_HOST}" pattern="^localtest\.me$" />
      </conditions>
      <action type="Redirect" 
        url="{MapProtocol:{HTTPS}}://www.localtest.me/{R:1}" />
    </rule>
  </rules>
  <rewriteMaps>
    <rewriteMap name="MapProtocol">
      <add key="on" value="https" />
      <add key="off" value="http" />
    </rewriteMap>
  </rewriteMaps>
</rewrite>

Option 3 – CACHE_URL, Multi-rule

If you have many rules that will use the protocol, you can create your own server variable which can be used in subsequent rules. This option is no easier to set up than Option 2 above, but you can use it if you prefer the easier to remember syntax of {HTTP_PROTOCOL} vs. {MapProtocol:{HTTPS}}.

The potential issue with this rule is that if you don’t have access to the server level (e.g. in a shared environment) then you cannot set server variables without permission.

First, create a rule and place it at the top of the set of rules.  You can create this at the server, site or subfolder level.  However, if you create it at the site or subfolder level then the HTTP_PROTOCOL server variable needs to be approved at the server level.  This can be achieved in IIS Manager by navigating to URL Rewrite at the server level, clicking on “View Server Variables” from the Actions pane, and added HTTP_PROTOCOL. If you create the rule at the server level then this step is not necessary. 

Following is an example of the first rule to create the HTTP_PROTOCOL and then a rule that uses it.  The Create HTTP_PROTOCOL rule only needs to be created once on the server.

<rule name="Create HTTP_PROTOCOL">
  <match url=".*" />
  <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
    <add input="{CACHE_URL}" pattern="^(.+)://" />
  </conditions>
  <serverVariables>
    <set name="HTTP_PROTOCOL" value="{C:1}" />
  </serverVariables>
  <action type="None" />
</rule>
 
<rule name="Redirect to www" stopProcessing="true">
  <match url="(.*)" />
  <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
    <add input="{HTTP_HOST}" pattern="^localtest\.me$" />
  </conditions>
  <action type="Redirect" url="{HTTP_PROTOCOL}://www.localtest.me/{R:1}" />
</rule>

Option 4 – Multi-rule

Just to be complete I’ll include an example of how to achieve the same thing with multiple rules. I don’t see any reason to use it over the previous examples, but I’ll include an example anyway.  Note that it will only work with the “Match All” setting for the conditions.

<rule name="Redirect to www - http" stopProcessing="true">
  <match url="(.*)" />
  <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
    <add input="{HTTP_HOST}" pattern="^localtest\.me$" />
    <add input="{HTTPS}" pattern="off" />
  </conditions>
  <action type="Redirect" url="http://www.localtest.me/{R:1}" />
</rule>
<rule name="Redirect to www - https" stopProcessing="true">
  <match url="(.*)" />
  <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
    <add input="{HTTP_HOST}" pattern="^localtest\.me$" />
    <add input="{HTTPS}" pattern="on" />
  </conditions>
  <action type="Redirect" url="https://www.localtest.me/{R:1}" />
</rule>

Conclusion

Above are four working examples of methods to call the protocol (HTTP or HTTPS) from the action of a URL Rewrite rule.  You can use whichever method you most prefer.  I’ve listed them in the order that I favor them, although I could see some people preferring Option 2 as their first choice.  In any of the cases, hopefully you can use this as a reference for when you need to use the protocol in the rule’s action when writing your URL Rewrite rules.

Further information:

No Comments